diff --git a/app/code/Magento/Authorizenet/Model/Source/Cctype.php b/app/code/Magento/Authorizenet/Model/Source/Cctype.php index c6a83e19bd32733781228f873bd964558bd894be..a6e173474ae869f2ed40520ed2ccf8197f752e4d 100644 --- a/app/code/Magento/Authorizenet/Model/Source/Cctype.php +++ b/app/code/Magento/Authorizenet/Model/Source/Cctype.php @@ -17,6 +17,6 @@ class Cctype extends PaymentCctype */ public function getAllowedTypes() { - return ['VI', 'MC', 'AE', 'DI']; + return ['VI', 'MC', 'AE', 'DI', 'JCB', 'DN']; } } diff --git a/app/code/Magento/Authorizenet/etc/config.xml b/app/code/Magento/Authorizenet/etc/config.xml index 70b413b5c1a3936c4efe3542f992b1dfead63999..5c48b88e0829965dd6f9a7d2d66f7373a7bb6990 100644 --- a/app/code/Magento/Authorizenet/etc/config.xml +++ b/app/code/Magento/Authorizenet/etc/config.xml @@ -10,7 +10,7 @@ <payment> <authorizenet_directpost> <active>0</active> - <cctypes>AE,VI,MC,DI</cctypes> + <cctypes>AE,VI,MC,DI,JCB,DN</cctypes> <debug>0</debug> <email_customer>0</email_customer> <login backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> diff --git a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml index bf674f8d5f3b480f0892b7273a04e82e2fb804ed..cb7200cad183c26c1d5c5ec8d4baae1477d57feb 100644 --- a/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml +++ b/app/code/Magento/Authorizenet/view/adminhtml/templates/directpost/info.phtml @@ -35,8 +35,12 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); </label> <div class="admin__field-control"> <select id="<?php /* @noEscape */ echo $code; ?>_cc_type" name="payment[cc_type]" - class="required-entry validate-cc-type-select admin__control-select"> - <option value=""></option> + class="admin__control-select" + data-validate="{ + 'required':true, + 'validate-cc-type-select':'#<?php /* @noEscape */ echo $code; ?>_cc_number' + }"> + <option value=""><?php echo $block->escapeHtml(__('Please Select')); ?></option> <?php foreach ($block->getCcAvailableTypes() as $typeCode => $typeName): ?> <option value="<?php echo $block->escapeHtml($typeCode); ?>" <?php if ($typeCode == $ccType): ?>selected="selected"<?php endif; ?>> @@ -46,19 +50,25 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); </select> </div> </div> + <div class="admin__field _required"> <label for="<?php /* @noEscape */ echo $code; ?>_cc_number" class="admin__field-label"> <span><?php echo $block->escapeHtml(__('Credit Card Number')); ?></span> </label> - <div class="admin__field-control"> <input type="text" id="<?php /* @noEscape */ echo $code; ?>_cc_number" name="payment[cc_number]" - class="input-text required-entry validate-cc-number admin__control-text" + data-validate="{ + 'required-number':true, + 'validate-cc-number':'#<?php /* @noEscape */ echo $code; ?>_cc_type', + 'validate-cc-type':'#<?php /* @noEscape */ echo $code; ?>_cc_type' + }" + class="admin__control-text" value="<?php /* @noEscape */ echo $block->getInfoData('cc_number'); ?>"/> </div> </div> - <div class="admin__field _required"> + + <div class="admin__field _required field-date" id="<?php /* @noEscape */ echo $code; ?>_cc_type_exp_div"> <label for="<?php /* @noEscape */ echo $code; ?>_expiration" class="admin__field-label"> <span><?php echo $block->escapeHtml(__('Expiration Date')); ?></span> </label> @@ -66,7 +76,11 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); <div class="admin__field-control"> <select id="<?php /* @noEscape */ echo $code; ?>_expiration" name="payment[cc_exp_month]" - class="validate-cc-exp required-entry admin__control-select admin__control-select-month"> + class="admin__control-select admin__control-select-month" + data-validate="{ + 'required':true, + 'validate-cc-exp':'#<?php /* @noEscape */ echo $code; ?>_expiration_yr' + }"> <?php foreach ($block->getCcMonths() as $k => $v): ?> <option value="<?php echo $block->escapeHtml($k); ?>" <?php if ($k == $ccExpMonth): ?>selected="selected"<?php endif; ?>> @@ -76,7 +90,9 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); </select> <select id="<?php /* @noEscape */ echo $code; ?>_expiration_yr" name="payment[cc_exp_year]" - class="required-entry admin__control-select admin__control-select-year"> + class="admin__control-select admin__control-select-year" + data-container="<?php /* @noEscape */ echo $code; ?>-cc-year" + data-validate="{required:true}"> <?php foreach ($block->getCcYears() as $k => $v): ?> <option value="<?php /* @noEscape */ echo $k ? $block->escapeHtml($k) : ''; ?>" <?php if ($k == $ccExpYear): ?>selected="selected"<?php endif; ?>> @@ -86,17 +102,27 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); </select> </div> </div> + <?php if ($block->hasVerification()): ?> - <div class="admin__field _required"> - <label for="<?php /* @noEscape */ echo $code; ?>_cc_cid"> + <div class="admin__field _required field-cvv"> + <label class="admin__field-label" + for="<?php /* @noEscape */ echo $code; ?>_cc_cid" + id="<?php /* @noEscape */ echo $code; ?>_cc_type_cvv_div"> <span><?php echo $block->escapeHtml(__('Card Verification Number')); ?></span> </label> <div class="admin__field-control"> <input type="text" - class="required-entry input-text validate-cc-cvn admin__control-text" + data-container="<?php /* @noEscape */ echo $code; ?>-cc-cvv" + title="<?php echo $block->escapeHtml(__('Card Verification Number')); ?>" + class="admin__control-text cvv" id="<?php /* @noEscape */ echo $code; ?>_cc_cid" name="payment[cc_cid]" - value="<?php /* @noEscape */ echo $block->getInfoData('cc_cid') ?>"/> + value="<?php /* @noEscape */ echo $block->getInfoData('cc_cid') ?>" + data-validate="{ + 'required-number':true, + 'validate-cc-cvn':'#<?php /* @noEscape */ echo $code; ?>_cc_type' + }" + autocomplete="off"/> </div> </div> <?php endif; ?> @@ -105,7 +131,7 @@ $ccExpYear = $block->getInfoData('cc_exp_year'); require([ 'prototype', 'Magento_Sales/order/create/scripts', - "Magento_Sales/order/create/form", + 'Magento_Sales/order/create/form', 'Magento_Authorizenet/js/direct-post' ], function(){ diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml index 459dd377e36bac315d1d56f6e8dd4e499986c149..760bba1fc12f4c1c3096ad6f422bf645ed174d06 100644 --- a/app/code/Magento/Backend/etc/config.xml +++ b/app/code/Magento/Backend/etc/config.xml @@ -11,6 +11,9 @@ <template> <minify_html>0</minify_html> </template> + <static> + <sign>1</sign> + </static> </dev> <system> <media_storage_configuration> diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php index d5488ecc5d085ee4a5d4b535ccf24040dbaee2a6..ac8cb91222fba47c7c686c10b45d6b81e87db2eb 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php @@ -14,6 +14,12 @@ namespace Magento\Catalog\Block\Adminhtml\Helper\Form\Wysiwyg; use Magento\Backend\Block\Widget\Form; use Magento\Backend\Block\Widget\Form\Generic; +/** + * Class Content + * + * @deprecated + * @see \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav + */ class Content extends Generic { /** diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php index d402c81becf1ce21e636b6d36082a47ddcc2f155..d59492c4065e72706f8c1a168e4a33b1c2c9e825 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -199,6 +199,9 @@ class Helper $customOptions = []; foreach ($options as $customOptionData) { if (empty($customOptionData['is_delete'])) { + if (empty($customOptionData['option_id'])) { + $customOptionData['option_id'] = null; + } if (isset($customOptionData['values'])) { $customOptionData['values'] = array_filter($customOptionData['values'], function ($valueData) { return empty($valueData['is_delete']); @@ -206,7 +209,6 @@ class Helper } $customOption = $this->getCustomOptionFactory()->create(['data' => $customOptionData]); $customOption->setProductSku($product->getSku()); - $customOption->setOptionId(null); $customOptions[] = $customOption; } } @@ -255,7 +257,7 @@ class Helper foreach ($linkTypes as $linkType => $readonly) { if (isset($links[$linkType]) && !$readonly) { - foreach ((array) $links[$linkType] as $linkData) { + foreach ((array)$links[$linkType] as $linkData) { if (empty($linkData['id'])) { continue; } @@ -321,9 +323,11 @@ class Helper if (isset($option['values']) && isset($overwriteOptions[$optionId]['values'])) { foreach ($option['values'] as $valueIndex => $value) { - $valueId = $value['option_type_id']; - $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); - $option['values'][$valueIndex] = $value; + if (isset($value['option_type_id'])) { + $valueId = $value['option_type_id']; + $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); + $option['values'][$valueIndex] = $value; + } } } @@ -347,6 +351,9 @@ class Helper foreach ($overwriteOptions[$optionId] as $fieldName => $overwrite) { if ($overwrite && isset($option[$fieldName]) && isset($option['default_' . $fieldName])) { $option[$fieldName] = $option['default_' . $fieldName]; + if ('title' == $fieldName) { + $option['is_delete_store_title'] = 1; + } } } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php index 6dae249865157d1c5960c8be6cefa9bdf27b3126..43114cb83ad149ff07a2310ec1924dcd6f9faa25 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php @@ -84,6 +84,11 @@ class MassStatus extends \Magento\Catalog\Controller\Adminhtml\Product $productIds = $collection->getAllIds(); $storeId = (int) $this->getRequest()->getParam('store', 0); $status = (int) $this->getRequest()->getParam('status'); + $filters = (array)$this->getRequest()->getParam('filters', []); + + if (isset($filters['store_id'])) { + $storeId = (int)$filters['store_id']; + } try { $this->_validateMassStatus($productIds, $status); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php index c984840e2067daa0f43f06319e9c3a68b1287012..6e432447263ee9d4d6240a6c104b6c599db1e81c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php @@ -101,6 +101,9 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Set } $model->save(); $this->messageManager->addSuccess(__('You saved the attribute set.')); + } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $hasError = true; } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); $hasError = true; diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php index 3a7ce70cbff528239712a353cb5f939f9a0878c4..69f4a613a5b9555e2c46038c2a6cc620b291ef60 100644 --- a/app/code/Magento/Catalog/Helper/Product/Compare.php +++ b/app/code/Magento/Catalog/Helper/Product/Compare.php @@ -231,6 +231,8 @@ class Compare extends \Magento\Framework\Url\Helper\Data $data = [ \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => '', 'product' => $product->getId(), + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove this item from your Compare Products list?') ]; return $this->postHelper->getPostData($this->getRemoveUrl(), $data); } @@ -254,6 +256,8 @@ class Compare extends \Magento\Framework\Url\Helper\Data { $params = [ \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => '', + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove all items from your Compare Products list?'), ]; return $this->postHelper->getPostData($this->getClearListUrl(), $params); } diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index b1f9dbf8c327a46176208cec90f8130065d8400a..9e855e05c7ce388dd52c11829882d5b46f634d02 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -65,7 +65,7 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements */ const TREE_ROOT_ID = 1; - const CACHE_TAG = 'catalog_category'; + const CACHE_TAG = 'cat_c'; /**#@+ * Constants diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index 0036b3b2ab54a5a2f4ff104dbe7b8a3da4526d28..4375092591d194962b108e2ba0351b1a03fa7e5c 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -221,7 +221,11 @@ class FlatTableBuilder unset($tables[$entityTableName]); - $allColumns = array_merge(['entity_id', 'type_id', 'attribute_set_id'], $columnsList); + $allColumns = array_values( + array_unique( + array_merge(['entity_id', $linkField, 'type_id', 'attribute_set_id'], $columnsList) + ) + ); /* @var $status \Magento\Eav\Model\Entity\Attribute */ $status = $this->_productIndexerHelper->getAttribute('status'); @@ -263,7 +267,7 @@ class FlatTableBuilder $select->joinLeft( $temporaryTableName, - "e.entity_id = " . $temporaryTableName . ".entity_id", + sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryTableName), $columnsNames ); $allColumns = array_merge($allColumns, $columnsNames); @@ -277,7 +281,7 @@ class FlatTableBuilder if (!empty($columnValueNames)) { $select->joinLeft( $temporaryValueTableName, - "e.${linkField} = " . $temporaryValueTableName . ".entity_id", + sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryTableName), $columnValueNames ); $allColumns = array_merge($allColumns, $columnValueNames); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php new file mode 100644 index 0000000000000000000000000000000000000000..192f411c93b4ea48c1b753d88f586a25aca41838 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Indexer\Product\Flat\Table; + +/** + * Class Builder + */ +class Builder implements BuilderInterface +{ + /** + * @var \Magento\Framework\DB\Ddl\Table + */ + private $tableInstance; + + /** + * Builder constructor. + * + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection + * @param string $tableName + */ + public function __construct(\Magento\Framework\DB\Adapter\AdapterInterface $connection, $tableName) + { + $this->tableInstance = $connection->newTable($tableName); + } + + /** + * @inheritdoc + */ + public function addColumn($name, $type, $size = null, $options = [], $comment = null) + { + $this->tableInstance->addColumn($name, $type, $size, $options, $comment); + return $this; + } + + /** + * @inheritdoc + */ + public function getTable() + { + return $this->tableInstance; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b24db9ee74334b743988b4a964004c41a5c610c7 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Indexer\Product\Flat\Table; + +/** + * Interface BuilderInterface + */ +interface BuilderInterface +{ + /** + * Adds column to table. + * + * $options contains additional options for columns. Supported values are: + * - 'unsigned', for number types only. Default: FALSE. + * - 'precision', for numeric and decimal only. Default: taken from $size, if not set there then 0. + * - 'scale', for numeric and decimal only. Default: taken from $size, if not set there then 10. + * - 'default'. Default: not set. + * - 'nullable'. Default: TRUE. + * - 'primary', add column to primary index. Default: do not add. + * - 'primary_position', only for column in primary index. Default: count of primary columns + 1. + * - 'identity' or 'auto_increment'. Default: FALSE. + * + * @param string $name the column name + * @param string $type the column data type + * @param string|int|array $size the column length + * @param array $options array of additional options + * @param string $comment column description + * @return $this + * @throws \Zend_Db_Exception + */ + public function addColumn($name, $type, $size = null, $options = [], $comment = null); + + /** + * @return \Magento\Framework\DB\Ddl\Table + */ + public function getTable(); +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php index fdc405b27d3fd8b8b41c69395397c38235c8412e..d00a720a91ddefdad1036c52a0c12d6c5f10d7b3 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php @@ -5,7 +5,7 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Flat; -use Magento\Framework\App\ResourceConnection; +use Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterfaceFactory; /** * Class TableBuilder @@ -32,6 +32,11 @@ class TableBuilder */ protected $resource; + /** + * @var BuilderInterfaceFactory + */ + private $tableBuilderFactory; + /** * Check whether builder was executed * @@ -123,17 +128,27 @@ class TableBuilder $valueTables = []; if (!empty($columns)) { $valueTableName = $tableName . $valueFieldSuffix; - $temporaryTable = $this->_connection->newTable($tableName); - $valueTemporaryTable = $this->_connection->newTable($valueTableName); + $temporaryTableBuilder = $this->getTableBuilderFactory()->create( + [ + 'connection' => $this->_connection, + 'tableName' => $tableName + ] + ); + $valueTemporaryTableBuilder = $this->getTableBuilderFactory()->create( + [ + 'connection' => $this->_connection, + 'tableName' => $valueTableName + ] + ); $flatColumns = $this->_productIndexerHelper->getFlatColumns(); - $temporaryTable->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $temporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); - $temporaryTable->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT); + $temporaryTableBuilder->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT); - $temporaryTable->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $temporaryTableBuilder->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); - $valueTemporaryTable->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $valueTemporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ foreach ($columns as $columnName => $attribute) { @@ -145,7 +160,7 @@ class TableBuilder $column = $column[$attributeCode]; } - $temporaryTable->addColumn( + $temporaryTableBuilder->addColumn( $columnName, $column['type'], isset($column['length']) ? $column['length'] : null @@ -154,7 +169,7 @@ class TableBuilder $columnValueName = $attributeCode . $valueFieldSuffix; if (isset($flatColumns[$columnValueName])) { $columnValue = $flatColumns[$columnValueName]; - $valueTemporaryTable->addColumn( + $valueTemporaryTableBuilder->addColumn( $columnValueName, $columnValue['type'], isset($columnValue['length']) ? $columnValue['length'] : null @@ -162,11 +177,11 @@ class TableBuilder } } $this->_connection->dropTemporaryTable($tableName); - $this->_connection->createTemporaryTable($temporaryTable); + $this->_connection->createTemporaryTable($temporaryTableBuilder->getTable()); - if (count($valueTemporaryTable->getColumns()) > 1) { + if (count($valueTemporaryTableBuilder->getTable()->getColumns()) > 1) { $this->_connection->dropTemporaryTable($valueTableName); - $this->_connection->createTemporaryTable($valueTemporaryTable); + $this->_connection->createTemporaryTable($valueTemporaryTableBuilder->getTable()); $valueTables[$valueTableName] = $valueTableName; } } @@ -197,7 +212,8 @@ class TableBuilder if (!empty($columns)) { $select = $this->_connection->select(); $temporaryEntityTable = $this->_getTemporaryTableName($tableName); - $idsColumns = ['entity_id', 'type_id', 'attribute_set_id']; + $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $idsColumns = array_unique([$metadata->getLinkField(), 'entity_id', 'type_id', 'attribute_set_id']); $columns = array_merge($idsColumns, array_keys($columns)); @@ -261,7 +277,7 @@ class TableBuilder ); $temporaryTableName = $this->_getTemporaryTableName($tableName); $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix; - $keyColumn = ['entity_id']; + $keyColumn = array_unique([$metadata->getLinkField(), 'entity_id']); $columns = array_merge($keyColumn, array_keys($columnsList)); $valueColumns = $keyColumn; $flatColumns = $this->_productIndexerHelper->getFlatColumns(); @@ -333,6 +349,19 @@ class TableBuilder } } + /** + * @return BuilderInterfaceFactory + */ + private function getTableBuilderFactory() + { + if (null === $this->tableBuilderFactory) { + $this->tableBuilderFactory = \Magento\Framework\App\ObjectManager::getInstance() + ->get(BuilderInterfaceFactory::class); + } + + return $this->tableBuilderFactory; + } + /** * @return \Magento\Framework\EntityManager\MetadataPool */ diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 49df6fd57c323bc1f12f65c590ff39ff4e041123..9e9f18e0113717ad6853ea5d4512661a897e926c 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -58,12 +58,12 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements /** * Product cache tag */ - const CACHE_TAG = 'catalog_product'; + const CACHE_TAG = 'cat_p'; /** * Category product relation cache tag */ - const CACHE_PRODUCT_CATEGORY_TAG = 'catalog_category_product'; + const CACHE_PRODUCT_CATEGORY_TAG = 'cat_c_p'; /** * Product Store Id diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php index 327729cf83695931a8c378dc91f41ab66a9e7d69..290770bd296c3bf9ef8854de86008674776ae2b7 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php @@ -73,9 +73,12 @@ class Sku extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend { $attribute = $this->getAttribute(); $entity = $attribute->getEntity(); - $increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object); $attributeValue = $object->getData($attribute->getAttributeCode()); + $increment = null; while (!$entity->checkAttributeUniqueValue($attribute, $object)) { + if ($increment === null) { + $increment = $this->_getLastSimilarAttributeValueIncrement($attribute, $object); + } $sku = trim($attributeValue); if (strlen($sku . '-' . ++$increment) > self::SKU_MAX_LENGTH) { $sku = substr($sku, 0, -strlen($increment) - 1); diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php index ada156f571b0e60c8ff1a0ada71b308f9b62b38f..cbf0464ca366158d4d3e4d75f65e4435f0834502 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php @@ -58,6 +58,11 @@ class CreateHandler implements ExtensionInterface */ protected $fileStorageDb; + /** + * @var array + */ + private $mediaAttributeCodes; + /** * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository @@ -145,9 +150,11 @@ class CreateHandler implements ExtensionInterface } /* @var $mediaAttribute \Magento\Catalog\Api\Data\ProductAttributeInterface */ - foreach ($this->mediaConfig->getMediaAttributeCodes() as $mediaAttrCode) { + foreach ($this->getMediaAttributeCodes() as $mediaAttrCode) { $attrData = $product->getData($mediaAttrCode); - + if (empty($attrData) && empty($clearImages) && empty($newImages) && empty($existImages)) { + continue; + } if (in_array($attrData, $clearImages)) { $product->setData($mediaAttrCode, 'no_selection'); } @@ -160,12 +167,13 @@ class CreateHandler implements ExtensionInterface if (in_array($attrData, array_keys($existImages))) { $product->setData($mediaAttrCode . '_label', $existImages[$attrData]['label']); } - - $product->addAttributeUpdate( - $mediaAttrCode, - $product->getData($mediaAttrCode), - $product->getStoreId() - ); + if (!empty($product->getData($mediaAttrCode))) { + $product->addAttributeUpdate( + $mediaAttrCode, + $product->getData($mediaAttrCode), + $product->getStoreId() + ); + } } $product->setData($attrCode, $value); @@ -393,4 +401,17 @@ class CreateHandler implements ExtensionInterface ); } } + + /** + * Get Media Attribute Codes cached value + * + * @return array + */ + private function getMediaAttributeCodes() + { + if ($this->mediaAttributeCodes === null) { + $this->mediaAttributeCodes = $this->mediaConfig->getMediaAttributeCodes(); + } + return $this->mediaAttributeCodes; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php index 3df4d9813a668b6fc81d7706412e1cf4e671c5e5..167dc2be15b292c4fabd9983911cd54cba89e62c 100644 --- a/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php @@ -40,7 +40,6 @@ class SaveHandler Link $linkResource, ProductLinkRepositoryInterface $productLinkRepository ) { - $this->metadataPool = $metadataPool; $this->linkResource = $linkResource; $this->productLinkRepository = $productLinkRepository; @@ -54,12 +53,18 @@ class SaveHandler */ public function execute($entityType, $entity) { - /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ - foreach ($this->productLinkRepository->getList($entity) as $link) { - $this->productLinkRepository->delete($link); + $link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField()); + if ($this->linkResource->hasProductLinks($link)) { + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity*/ + foreach ($this->productLinkRepository->getList($entity) as $link) { + $this->productLinkRepository->delete($link); + } } - foreach ($entity->getProductLinks() as $link) { - $this->productLinkRepository->save($link); + $productLinks = $entity->getProductLinks(); + if (count($productLinks) > 0) { + foreach ($entity->getProductLinks() as $link) { + $this->productLinkRepository->save($link); + } } return $entity; } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php index d48bed8e3ccd08718e02c57d764e89c147ebf161..946bebd93c94029907b0ac7ed13dc6c1610b376c 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php @@ -137,10 +137,33 @@ class Repository implements \Magento\Catalog\Api\ProductCustomOptionRepositoryIn if (!$productSku) { throw new CouldNotSaveException(__('ProductSku should be specified')); } + /** @var \Magento\Catalog\Model\Product $product */ $product = $this->productRepository->get($productSku); $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); $option->setData('product_id', $product->getData($metadata->getLinkField())); - $option->setOptionId(null); + $option->setData('store_id', $product->getStoreId()); + + if ($option->getOptionId()) { + $options = $product->getOptions(); + if (!$options) { + $options = $this->getProductOptions($product); + } + + $persistedOption = array_filter($options, function ($iOption) use ($option) { + return $option->getOptionId() == $iOption->getOptionId(); + }); + $persistedOption = reset($persistedOption); + + if (!$persistedOption) { + throw new NoSuchEntityException(); + } + $originalValues = $persistedOption->getValues(); + $newValues = $option->getData('values'); + if ($newValues) { + $newValues = $this->markRemovedValues($newValues, $originalValues); + $option->setData('values', $newValues); + } + } $option->save(); return $option; } diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php index 7c32232b6591aeb9a99a9b63f0b40300cd11dfe4..1c8c5b018ddf3b19642242431205756795fce45f 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php @@ -20,7 +20,6 @@ class SaveHandler implements ExtensionInterface /** * @param OptionRepository $optionRepository - * @param MetadataPool $metadataPool */ public function __construct( OptionRepository $optionRepository @@ -36,15 +35,28 @@ class SaveHandler implements ExtensionInterface */ public function execute($entity, $arguments = []) { + $options = $entity->getOptions(); + $optionIds = []; + + if ($options) { + $optionIds = array_map(function ($option) { + /** @var \Magento\Catalog\Model\Product\Option $option */ + return $option->getOptionId(); + }, $options); + } + /** @var \Magento\Catalog\Api\Data\ProductInterface $entity */ foreach ($this->optionRepository->getProductOptions($entity) as $option) { - $this->optionRepository->delete($option); + if (!in_array($option->getOptionId(), $optionIds)) { + $this->optionRepository->delete($option); + } } - if ($entity->getOptions()) { - foreach ($entity->getOptions() as $option) { + if ($options) { + foreach ($options as $option) { $this->optionRepository->save($option); } } + return $entity; } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File.php index d974174ef7088e4a14a9e031e7e266d827ceb13d..70c20a4e314903ce9defb0e40f50c912a83d7307 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File.php @@ -5,6 +5,7 @@ */ namespace Magento\Catalog\Model\Product\Option\Type; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Magento\Framework\Exception\LocalizedException; use Magento\Catalog\Model\Product\Exception as ProductException; @@ -69,17 +70,23 @@ class File extends \Magento\Catalog\Model\Product\Option\Type\DefaultType */ protected $validatorFile; + /** + * @var Filesystem + */ + private $filesystem; + /** * @param \Magento\Checkout\Model\Session $checkoutSession * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Item\OptionFactory $itemOptionFactory - * @param \Magento\Catalog\Model\Product\Option\UrlBuilder $urlBuilder - * @param \Magento\Framework\Escaper $escaper * @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase * @param File\ValidatorInfo $validatorInfo * @param File\ValidatorFile $validatorFile + * @param \Magento\Catalog\Model\Product\Option\UrlBuilder $urlBuilder + * @param \Magento\Framework\Escaper $escaper * @param array $data - * @throws \Magento\Framework\Exception\FileSystemException + * @param Filesystem $filesystem + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Checkout\Model\Session $checkoutSession, @@ -90,12 +97,15 @@ class File extends \Magento\Catalog\Model\Product\Option\Type\DefaultType \Magento\Catalog\Model\Product\Option\Type\File\ValidatorFile $validatorFile, \Magento\Catalog\Model\Product\Option\UrlBuilder $urlBuilder, \Magento\Framework\Escaper $escaper, - array $data = [] + array $data = [], + Filesystem $filesystem = null ) { $this->_itemOptionFactory = $itemOptionFactory; $this->_urlBuilder = $urlBuilder; $this->_escaper = $escaper; $this->_coreFileStorageDatabase = $coreFileStorageDatabase; + $this->filesystem = $filesystem ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Filesystem::class); + $this->_rootDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); $this->validatorInfo = $validatorInfo; $this->validatorFile = $validatorFile; parent::__construct($checkoutSession, $scopeConfig, $data); diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php index ba7103a5eada1acac077ae3ce432e0187e1fbe22..d1a317be7c7e0c2d09cd0b5bbddf6ab3844c96d6 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php @@ -51,6 +51,9 @@ class Select extends DefaultValidator $storeId = $option->getProduct()->getStoreId(); } foreach ($values as $value) { + if (isset($value['is_delete']) && (bool)$value['is_delete']) { + continue; + } $type = isset($value['price_type']) ? $value['price_type'] : null; $price = isset($value['price']) ? $value['price'] : null; $title = isset($value['title']) ? $value['title'] : null; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php index 55a14e26333547543a14e22b7dcbc0342442596d..55c7c74e9a8e62bf14233f8c8b9b90309adfee66 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php @@ -200,7 +200,7 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu 'store_id', $this->getOption()->getStoreId() ); - $this->unsetData('option_type_id'); + if ($this->getData('is_delete') == '1') { if ($this->getId()) { $this->deleteValues($this->getId()); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 407c2027923de4c683272773d519cf1adc7b1917..e743c5d384bdd539a15c4f666b36ce0f43f7f4d0 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -238,7 +238,9 @@ class Category extends AbstractResource if (!$object->getChildrenCount()) { $object->setChildrenCount(0); } - + $object->setAttributeSetId( + $object->getAttributeSetId() ?: $this->getEntityType()->getDefaultAttributeSetId() + ); if ($object->isObjectNew()) { if ($object->getPosition() === null) { $object->setPosition($this->_getMaxPosition($object->getPath()) + 1); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index 99c4316d20740edc4d3d6415bbfb06a344e7e5c7..8e93868dc7e51916a1c44997ad98045edbe1bd4f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -351,14 +351,11 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute implements */ public function getApplyTo() { - if ($this->getData(self::APPLY_TO)) { - if (is_array($this->getData(self::APPLY_TO))) { - return $this->getData(self::APPLY_TO); - } - return explode(',', $this->getData(self::APPLY_TO)); - } else { - return []; + $applyTo = $this->_getData(self::APPLY_TO) ?: []; + if (!is_array($applyTo)) { + $applyTo = explode(',', $applyTo); } + return $applyTo; } /** diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php index bbccabe5a61c969f74294398ad484e1b0d3c64f9..17bfa90e8842d4dc511d8d78f2dc23151a3934b3 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link.php @@ -96,6 +96,30 @@ class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $connection->fetchOne($select, $bind); } + /** + * Check if product has links. + * + * @param int $parentId ID of product + * @return bool + */ + public function hasProductLinks($parentId) + { + $connection = $this->getConnection(); + $select = $connection->select()->from( + $this->getMainTable(), + ['count' => new \Zend_Db_Expr('COUNT(*)')] + )->where( + 'product_id = :product_id' + ) ; + + return $connection->fetchOne( + $select, + [ + 'product_id' => $parentId + ] + ) > 0; + } + /** * Save Product Links process * diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php index 7b23e5979c8b841d9468f4c93596d4138c24d692..8dcdf441d7b85f014a5b6aa0398aa21592b81932 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php @@ -247,14 +247,21 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb $titleTableName = $this->getTable('catalog_product_option_title'); foreach ([\Magento\Store\Model\Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { $existInCurrentStore = $this->getColFromOptionTable($titleTableName, (int)$object->getId(), (int)$storeId); - $existInDefaultStore = $this->getColFromOptionTable( - $titleTableName, - (int)$object->getId(), - \Magento\Store\Model\Store::DEFAULT_STORE_ID - ); + $existInDefaultStore = (int)$storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID ? + $existInCurrentStore : + $this->getColFromOptionTable( + $titleTableName, + (int)$object->getId(), + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + if ($object->getTitle()) { + $isDeleteStoreTitle = (bool)$object->getData('is_delete_store_title'); if ($existInCurrentStore) { - if ($object->getStoreId() == $storeId) { + if ($isDeleteStoreTitle && (int)$storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + $connection->delete($titleTableName, ['option_title_id = ?' => $existInCurrentStore]); + + } elseif ($object->getStoreId() == $storeId) { $data = $this->_prepareDataForTable( new \Magento\Framework\DataObject(['title' => $object->getTitle()]), $titleTableName @@ -270,8 +277,13 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb } } else { // we should insert record into not default store only of if it does not exist in default store - if (($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) - || ($storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInCurrentStore) + if ( + ($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + ( + $storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && + !$existInCurrentStore && + !$isDeleteStoreTitle + ) ) { $data = $this->_prepareDataForTable( new \Magento\Framework\DataObject( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index 39a67f9722e185894640178329d008de10cb574e..4f3d65800d9457194f4b26817353cc1e281c0dd7 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -33,6 +33,11 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ protected $_config; + /** + * @var \Magento\Framework\Locale\FormatInterface + */ + private $localeFormat; + /** * Class constructor * @@ -91,8 +96,9 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected function _saveValuePrices(\Magento\Framework\Model\AbstractModel $object) { $priceTable = $this->getTable('catalog_product_option_type_price'); + $formattedPrice = $this->getLocaleFormatter()->getNumber($object->getPrice()); - $price = (double)sprintf('%F', $object->getPrice()); + $price = (double)sprintf('%F', $formattedPrice); $priceType = $object->getPriceType(); if ($object->getPrice() && $priceType) { @@ -231,6 +237,11 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb ); $optionTypeId = $this->getConnection()->fetchOne($select); $existInCurrentStore = $this->getOptionIdFromOptionTable($titleTable, (int)$object->getId(), (int)$storeId); + + if ($storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && $object->getData('is_delete_store_title')) { + $object->unsetData('title'); + } + if ($object->getTitle()) { if ($existInCurrentStore) { if ($storeId == $object->getStoreId()) { @@ -410,4 +421,19 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $object; } + + /** + * Get FormatInterface to convert price from string to number format + * + * @return \Magento\Framework\Locale\FormatInterface + * @deprecated + */ + private function getLocaleFormatter() + { + if ($this->localeFormat === null) { + $this->localeFormat = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Locale\FormatInterface::class); + } + return $this->localeFormat; + } } 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 38ef297e684267c17c128298147343c979c43f85..f420f140b35820e3cbcebf986ed305217523aab2 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php @@ -114,8 +114,8 @@ class ListProductTest extends \PHPUnit_Framework_TestCase public function testGetIdentities() { - $productTag = 'catalog_product_1'; - $categoryTag = 'catalog_category_product_1'; + $productTag = 'cat_p_1'; + $categoryTag = 'cat_c_p_1'; $this->productMock->expects($this->once()) ->method('getIdentities') diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php index d030cf456b6b13e9e8dfa240df6252d4016decd4..f0287e05a4b7b3b94f837e9adec3e4a43667e34c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ViewTest.php @@ -32,7 +32,7 @@ class ViewTest extends \PHPUnit_Framework_TestCase $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->productTypeConfig = $this->getMock(\Magento\Catalog\Model\ProductTypes\ConfigInterface::class); $this->registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); - $this->view = $helper->getObject( + $this->view = $helper->getObject( \Magento\Catalog\Block\Product\View::class, ['productTypeConfig' => $this->productTypeConfig, 'registry' => $this->registryMock] ); @@ -65,7 +65,7 @@ class ViewTest extends \PHPUnit_Framework_TestCase public function testGetIdentities() { - $productTags = ['catalog_product_1']; + $productTags = ['cat_p_1']; $product = $this->getMock(\Magento\Catalog\Model\Product::class, [], [], '', false); $category = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); @@ -84,6 +84,6 @@ class ViewTest extends \PHPUnit_Framework_TestCase ] ) ); - $this->assertEquals(['catalog_product_1', 'catalog_category_1'], $this->view->getIdentities()); + $this->assertEquals(['cat_p_1', 'cat_c_1'], $this->view->getIdentities()); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php index 389599f510f08961211d7cc212b757db9620547a..a4218e92b3486db76afc61254553b914610d99c3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php @@ -6,10 +6,11 @@ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization; use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory; -use Magento\Catalog\Api\ProductRepositoryInterface\Proxy as ProductRepository; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Model\ProductRepository; use Magento\Framework\App\RequestInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Api\Data\StoreInterface; @@ -17,7 +18,6 @@ use Magento\Store\Api\Data\WebsiteInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Stdlib\DateTime\Filter\Date as DateFilter; use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; -use Magento\Catalog\Api\Data\ProductCustomOptionInterface; use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks; /** @@ -95,7 +95,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase protected $customOptionFactoryMock; /** - * @var ProductCustomOptionInterface|\PHPUnit_Framework_MockObject_MockObject + * @var Option|\PHPUnit_Framework_MockObject_MockObject */ protected $customOptionMock; @@ -136,8 +136,6 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->getMock(); $this->productMock = $this->getMockBuilder(Product::class) ->setMethods([ - 'setData', - 'addData', 'getId', 'setWebsiteIds', 'isLockedAttribute', @@ -145,7 +143,6 @@ class HelperTest extends \PHPUnit_Framework_TestCase 'getAttributes', 'unlockAttribute', 'getOptionsReadOnly', - 'setOptions', 'setCanSaveCustomOptions', '__sleep', '__wakeup', @@ -159,9 +156,10 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->customOptionMock = $this->getMockBuilder(ProductCustomOptionInterface::class) + $this->customOptionMock = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() - ->getMockForAbstractClass(); + ->setMethods(null) + ->getMock(); $this->productLinksMock = $this->getMockBuilder(ProductLinks::class) ->disableOriginalConstructor() ->getMock(); @@ -196,14 +194,10 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ public function testInitialize() { - $this->customOptionMock->expects($this->once()) - ->method('setProductSku'); - $this->customOptionMock->expects($this->once()) - ->method('setOptionId'); - $optionsData = [ - 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1'], - 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1'], + 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1', 'option_id' => ''], + 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '13'], + 'option3' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '14'] ]; $productData = [ 'stock_data' => ['stock_data'], @@ -277,29 +271,38 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->method('getAttributes') ->willReturn($attributesArray); - $productData['category_ids'] = []; - $productData['website_ids'] = []; - unset($productData['options']); - - $this->productMock->expects($this->once()) - ->method('addData') - ->with($productData); - $this->productMock->expects($this->once()) + $this->productMock->expects($this->any()) ->method('getSku') ->willReturn('sku'); $this->productMock->expects($this->any()) ->method('getOptionsReadOnly') ->willReturn(false); + $firstExpectedCustomOption = clone $this->customOptionMock; + $firstExpectedCustomOption->setData($optionsData['option2']); + $secondExpectedCustomOption = clone $this->customOptionMock; + $secondExpectedCustomOption->setData($optionsData['option3']); $this->customOptionFactoryMock->expects($this->any()) ->method('create') - ->with(['data' => $optionsData['option2']]) - ->willReturn($this->customOptionMock); - $this->productMock->expects($this->once()) - ->method('setOptions') - ->with([$this->customOptionMock]); + ->willReturnMap([ + [ + ['data' => $optionsData['option2']], + $firstExpectedCustomOption + ], [ + ['data' => $optionsData['option3']], + $secondExpectedCustomOption + ] + ]); $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); + + $productOptions = $this->productMock->getOptions(); + $this->assertTrue(2 == count($productOptions)); + list($option2, $option3) = $productOptions; + $this->assertTrue($option2->getOptionId() == $optionsData['option2']['option_id']); + $this->assertTrue('sku' == $option2->getData('product_sku')); + $this->assertTrue($option3->getOptionId() == $optionsData['option3']['option_id']); + $this->assertTrue('sku' == $option2->getData('product_sku')); } /** @@ -362,9 +365,9 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 'option_id' => '5', 'key1' => 'val1', - 'key2' => 'val2', + 'title' => 'val2', 'default_key1' => 'val3', - 'default_key2' => 'val4', + 'default_title' => 'val4', 'values' => [ [ 'option_type_id' => '2', @@ -379,7 +382,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 5 => [ 'key1' => '0', - 'key2' => '1', + 'title' => '1', 'values' => [2 => ['key1' => 1]] ] ], @@ -387,9 +390,10 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 'option_id' => '5', 'key1' => 'val1', - 'key2' => 'val4', + 'title' => 'val4', 'default_key1' => 'val3', - 'default_key2' => 'val4', + 'default_title' => 'val4', + 'is_delete_store_title' => 1, 'values' => [ [ 'option_type_id' => '2', @@ -413,8 +417,9 @@ class HelperTest extends \PHPUnit_Framework_TestCase [ 'option_type_id' => '2', 'key1' => 'val1', - 'key2' => 'val2', - 'default_key1' => 'val11' + 'title' => 'val2', + 'default_key1' => 'val11', + 'default_title' => 'val22' ] ] ] @@ -423,7 +428,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase 7 => [ 'key1' => '1', 'key2' => '1', - 'values' => [2 => ['key1' => 1, 'key2' => 1]] + 'values' => [2 => ['key1' => 0, 'title' => 1]] ] ], [ @@ -435,9 +440,11 @@ class HelperTest extends \PHPUnit_Framework_TestCase 'values' => [ [ 'option_type_id' => '2', - 'key1' => 'val11', - 'key2' => 'val2', - 'default_key1' => 'val11' + 'key1' => 'val1', + 'title' => 'val22', + 'default_key1' => 'val11', + 'default_title' => 'val22', + 'is_delete_store_title' => 1 ] ] ] diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/MassStatusTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/MassStatusTest.php index 0fff613c56e2e4edc5a6765826730ffefac89f10..6f7809d89e5bda52429f5c365f1fdd8519996189 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/MassStatusTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/MassStatusTest.php @@ -6,32 +6,74 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product; +use Magento\Ui\Component\MassAction\Filter; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Catalog\Model\Indexer\Product\Price\Processor; +use Magento\Catalog\Controller\Adminhtml\Product\Builder; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Catalog\Model\Product\Action; + +/** + * Class MassStatusTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class MassStatusTest extends \Magento\Catalog\Test\Unit\Controller\Adminhtml\ProductTest { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Processor|\PHPUnit_Framework_MockObject_MockObject */ - protected $priceProcessor; + private $priceProcessorMock; - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\View\Result\Redirect */ - protected $resultRedirect; + /** + * @var Redirect|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectMock; + + /** + * @var Filter|\PHPUnit_Framework_MockObject_MockObject + */ + private $filterMock; + + /** + * @var Builder|\PHPUnit_Framework_MockObject_MockObject + */ + private $productBuilderMock; + + /** + * @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject + */ + private $abstractDbMock; + + /** + * @var Action|\PHPUnit_Framework_MockObject_MockObject + */ + private $actionMock; protected function setUp() { - $this->priceProcessor = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price\Processor::class) + $this->priceProcessorMock = $this->getMockBuilder(Processor::class) ->disableOriginalConstructor()->getMock(); + $this->productBuilderMock = $this->getMockBuilder(Builder::class) + ->setMethods(['build']) + ->disableOriginalConstructor() + ->getMock(); - $productBuilder = $this->getMockBuilder( - \Magento\Catalog\Controller\Adminhtml\Product\Builder::class - )->setMethods(['build'])->disableOriginalConstructor()->getMock(); - - $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)->disableOriginalConstructor() - ->setMethods(['getTypeId', 'getStoreId', '__sleep', '__wakeup'])->getMock(); - $product->expects($this->any())->method('getTypeId')->will($this->returnValue('simple')); - $product->expects($this->any())->method('getStoreId')->will($this->returnValue('1')); - $productBuilder->expects($this->any())->method('build')->will($this->returnValue($product)); + $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getTypeId', 'getStoreId', '__sleep', '__wakeup']) + ->getMock(); + $productMock->expects($this->any()) + ->method('getTypeId') + ->willReturn('simple'); + $productMock->expects($this->any()) + ->method('getStoreId') + ->willReturn('1'); + $this->productBuilderMock->expects($this->any()) + ->method('build') + ->willReturn($productMock); - $this->resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) + $this->resultRedirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) ->disableOriginalConstructor() ->getMock(); $resultFactory = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) @@ -41,47 +83,71 @@ class MassStatusTest extends \Magento\Catalog\Test\Unit\Controller\Adminhtml\Pro $resultFactory->expects($this->atLeastOnce()) ->method('create') ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($this->resultRedirect); + ->willReturn($this->resultRedirectMock); - $abstractDbMock = $this->getMockBuilder(\Magento\Framework\Data\Collection\AbstractDb::class) + $this->abstractDbMock = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->setMethods(['getAllIds', 'getResource']) ->getMock(); - $abstractDbMock->expects($this->any()) - ->method('getAllIds') - ->willReturn([]); - - $filterMock = $this->getMockBuilder(\Magento\Ui\Component\MassAction\Filter::class) + $this->filterMock = $this->getMockBuilder(\Magento\Ui\Component\MassAction\Filter::class) ->disableOriginalConstructor() ->setMethods(['getCollection']) ->getMock(); - $filterMock->expects($this->any()) - ->method('getCollection') - ->willReturn($abstractDbMock); - - $collectionFactoryMock = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class - ) + $this->actionMock = $this->getMockBuilder(Action::class) + ->disableOriginalConstructor() + ->getMock(); + + $collectionFactoryMock = + $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $collectionFactoryMock->expects($this->any()) ->method('create') - ->willReturn($abstractDbMock); + ->willReturn($this->abstractDbMock); + + $additionalParams = [ + 'resultFactory' => $resultFactory + ]; + /** @var \Magento\Backend\App\Action\Context $context */ + $context = $this->initContext($additionalParams, [[Action::class, $this->actionMock]]); - $additionalParams = ['resultFactory' => $resultFactory]; $this->action = new \Magento\Catalog\Controller\Adminhtml\Product\MassStatus( - $this->initContext($additionalParams), - $productBuilder, - $this->priceProcessor, - $filterMock, + $context, + $this->productBuilderMock, + $this->priceProcessorMock, + $this->filterMock, $collectionFactoryMock ); } public function testMassStatusAction() { - $this->priceProcessor->expects($this->once())->method('reindexList'); + $storeId = 1; + $status = \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED; + $filters = [ + 'store_id' => 2, + ]; + + $this->filterMock->expects($this->once()) + ->method('getCollection') + ->willReturn($this->abstractDbMock); + $this->abstractDbMock->expects($this->once()) + ->method('getAllIds') + ->willReturn([3]); + $this->request->expects($this->exactly(3)) + ->method('getParam') + ->willReturnMap([ + ['store', 0, $storeId], + ['status', null, $status], + ['filters', [], $filters] + ]); + $this->actionMock->expects($this->once()) + ->method('updateAttributes') + ->with([3], ['status' => $status], 2); + $this->priceProcessorMock->expects($this->once()) + ->method('reindexList'); + $this->action->execute(); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/ProductTest.php index 9a21de70b4f114fceb5950ea716c580068584669..524bdceae10e03cfc9678f10d19b881588ff8de8 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/ProductTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml; +use Magento\Catalog\Api\Data\ProductInterface; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -25,17 +27,33 @@ abstract class ProductTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject */ protected $request; + /** + * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + /** * Init context object * * @param array $additionalParams + * @param array $objectManagerMap Object Manager mappings * @return \PHPUnit_Framework_MockObject_MockObject */ - protected function initContext(array $additionalParams = []) + protected function initContext(array $additionalParams = [], array $objectManagerMap = []) { $productActionMock = $this->getMock(\Magento\Catalog\Model\Product\Action::class, [], [], '', false); - $objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('get')->will($this->returnValue($productActionMock)); + + $this->objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); + + if ($objectManagerMap) { + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap($objectManagerMap); + } + + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->willReturn($productActionMock); $block = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) ->disableOriginalConstructor()->getMockForAbstractClass(); @@ -93,7 +111,7 @@ abstract class ProductTest extends \PHPUnit_Framework_TestCase $this->context->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManager)); $this->context->expects($this->any())->method('getRequest')->will($this->returnValue($requestInterfaceMock)); $this->context->expects($this->any())->method('getResponse')->will($this->returnValue($responseInterfaceMock)); - $this->context->expects($this->any())->method('getObjectManager')->will($this->returnValue($objectManagerMock)); + $this->context->expects($this->any())->method('getObjectManager')->willReturn($this->objectManagerMock); $this->context->expects($this->any())->method('getMessageManager') ->will($this->returnValue($managerInterfaceMock)); diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php index 9f4d41aefd6bb8d1fd02d8990bcdd12c33191035..51388c3725ba9dc430137514ba3c3fc103237d1f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php @@ -115,7 +115,9 @@ class CompareTest extends \PHPUnit_Framework_TestCase $removeUrl = 'catalog/product_compare/remove'; $postParams = [ Action::PARAM_NAME_URL_ENCODED => '', - 'product' => $productId + 'product' => $productId, + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove this item from your Compare Products list?'), ]; //Verification @@ -156,7 +158,9 @@ class CompareTest extends \PHPUnit_Framework_TestCase //Data $clearUrl = 'catalog/product_compare/clear'; $postParams = [ - Action::PARAM_NAME_URL_ENCODED => '' + Action::PARAM_NAME_URL_ENCODED => '', + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove all items from your Compare Products list?'), ]; //Verification diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7e5a8305467e8e979a9ccc79d39d7ffdb70bb339 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php @@ -0,0 +1,204 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat; + +use Magento\Catalog\Api\Data\ProductInterface; + +/** + * Class FlatTableBuilderTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class FlatTableBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Catalog\Helper\Product\Flat\Indexer|\PHPUnit_Framework_MockObject_MockObject + */ + private $flatIndexerMock; + + /** + * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceMock; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $tableDataMock; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var \Magento\Framework\EntityManager\MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPoolMock; + + /** + * @var \Magento\Framework\EntityManager\EntityMetadataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataMock; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder + */ + private $flatTableBuilder; + + protected function setUp() + { + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->flatIndexerMock = $this->getMockBuilder(\Magento\Catalog\Helper\Product\Flat\Indexer::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->tableDataMock = $this->getMockBuilder( + \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface::class + )->disableOriginalConstructor()->getMockForAbstractClass(); + $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->metadataMock = $this->getMockBuilder( + \Magento\Framework\EntityManager\EntityMetadataInterface::class + )->disableOriginalConstructor()->getMockForAbstractClass(); + $this->metadataMock->expects($this->any())->method('getLinkField')->willReturn('entity_id'); + + $this->flatTableBuilder = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder::class, + [ + 'productIndexerHelper' => $this->flatIndexerMock, + 'resource' => $this->resourceMock, + 'config' => $this->scopeConfigMock, + 'storeManager' => $this->storeManagerMock, + 'tableData' => $this->tableDataMock, + '_connection' => $this->connectionMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->flatTableBuilder, + 'metadataPool', + $this->metadataPoolMock + ); + } + + public function testBuild() + { + list($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables) = [1, [], '', '', true]; + $tableName = 'catalog_product_entity'; + $attributeTable = 'catalog_product_entity_int'; + $temporaryTableName = 'catalog_product_entity_int_tmp_indexer'; + $temporaryValueTableName = 'catalog_product_entity_int_tmp_indexer'; + $linkField = 'entity_id'; + $statusId = 22; + $this->flatIndexerMock->expects($this->once())->method('getAttributes')->willReturn([]); + $this->flatIndexerMock->expects($this->exactly(3))->method('getFlatColumns') + ->willReturnOnConsecutiveCalls( + [], + [$linkField => []], + [$linkField => []] + ); + $this->flatIndexerMock->expects($this->once())->method('getFlatIndexes')->willReturn([]); + $statusAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flatIndexerMock->expects($this->once())->method('getTablesStructure') + ->willReturn( + [ + 'catalog_product_entity' => [ + $linkField => $statusAttributeMock + ], + 'catalog_product_entity_int' => [ + $linkField => $statusAttributeMock + ] + ] + ); + $this->flatIndexerMock->expects($this->atLeastOnce())->method('getTable') + ->withConsecutive( + [$tableName], + ['catalog_product_website'] + ) + ->willReturnOnConsecutiveCalls( + $tableName, + 'catalog_product_website' + ); + $this->flatIndexerMock->expects($this->once())->method('getAttribute') + ->with('status') + ->willReturn($statusAttributeMock); + $backendMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend::class) + ->disableOriginalConstructor() + ->getMock(); + $backendMock->expects($this->atLeastOnce())->method('getTable')->willReturn($attributeTable); + $statusAttributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn( + $backendMock + ); + $statusAttributeMock->expects($this->atLeastOnce())->method('getId')->willReturn($statusId); + $tableMock = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->any())->method('newTable')->willReturn($tableMock); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($selectMock); + $selectMock->expects($this->once())->method('from')->with( + ['et' => 'catalog_product_entity_tmp_indexer'], + [$linkField, 'type_id', 'attribute_set_id'] + )->willReturnSelf(); + $selectMock->expects($this->atLeastOnce())->method('joinInner')->willReturnSelf(); + $selectMock->expects($this->exactly(3))->method('joinLeft') + ->withConsecutive( + [ + ['dstatus' => $attributeTable], + sprintf( + 'e.%s = dstatus.%s AND dstatus.store_id = %s AND dstatus.attribute_id = %s', + $linkField, + $linkField, + $storeId, + $statusId + ), + [] + ], + [ + $temporaryTableName, + "e.{$linkField} = {$temporaryTableName}.{$linkField}", + [$linkField] + ], + [ + $temporaryValueTableName, + "e.{$linkField} = " . $temporaryValueTableName . ".{$linkField}", + [$linkField] + ] + )->willReturnSelf(); + $this->metadataPoolMock->expects($this->atLeastOnce())->method('getMetadata')->with(ProductInterface::class) + ->willReturn($this->metadataMock); + $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeManagerMock->expects($this->once())->method('getStore')->with($storeId)->willReturn($storeMock); + $this->flatTableBuilder->build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0a80250fbdf03153b52b4e509b90bee9bf7dde9c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Table; + +/** + * Class BuilderTest + */ +class BuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + public function testAddColumn() + { + $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $table = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) + ->disableOriginalConstructor() + ->getMock(); + $table->expects($this->once())->method('addColumn') + ->with('test', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER) + ->willReturnSelf(); + $tableName = 'test_table'; + $this->connectionMock->expects($this->once()) + ->method('newTable') + ->with($tableName) + ->willReturn($table); + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + /** + * @var $builder \Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder + */ + $builder = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder::class, + [ + 'connection' => $this->connectionMock, + 'tableName' => $tableName + ] + ); + $this->assertEquals($builder, $builder->addColumn('test', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php index c461e7a616d81c3e9a72a3031d979b9f44817de1..894596342722d392fd79b51a330341c0b14660f5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php @@ -8,6 +8,8 @@ namespace Magento\Catalog\Test\Unit\Model\Product\Option; use \Magento\Catalog\Model\Product\Option\Repository; +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; +use \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -30,10 +32,15 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase protected $optionResourceMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductCustomOptionInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $optionMock; + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $optionCollectionFactory; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -71,13 +78,10 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase '', false ); - $optionCollectionFactory = $this->getMock( - \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory::class, - ['create'], - [], - '', - false - ); + $this->optionCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); $metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) ->disableOriginalConstructor() ->getMock(); @@ -96,7 +100,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $this->optionRepository, [ 'optionFactory' => $optionFactory, - 'optionCollectionFactory' => $optionCollectionFactory, + 'collectionFactory' => $this->optionCollectionFactory, 'metadataPool' => $metadataPool ] ); @@ -240,4 +244,75 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase } } } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + * @expectedExceptionMessage ProductSku should be specified + */ + public function testSaveCouldNotSaveException() + { + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn(null); + $this->optionRepository->save($this->optionMock); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ + public function testSaveNoSuchEntityException() + { + $productSku = 'simple_product'; + $optionId = 1; + $productOptionId = 2; + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn($productSku); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($this->productMock); + $productOption = clone $this->optionMock; + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $productOption->expects($this->any())->method('getOptionId')->willReturn($productOptionId); + $this->productMock->expects($this->once())->method('getOptions')->willReturn([$productOption]); + $this->optionRepository->save($this->optionMock); + } + + public function testSave() + { + $productSku = 'simple_product'; + $optionId = 1; + $originalValue1 = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option::class) + ->disableOriginalConstructor() + ->getMock(); + $originalValue2 = clone $originalValue1; + $originalValue3 = clone $originalValue1; + + $originalValue1->expects($this->at(0))->method('getData')->with('option_type_id')->willReturn(10); + $originalValue1->expects($this->once())->method('setData')->with('is_delete', 1); + $originalValue2->expects($this->once())->method('getData')->with('option_type_id')->willReturn(4); + $originalValue3->expects($this->once())->method('getData')->with('option_type_id')->willReturn(5); + + $this->optionMock->expects($this->once())->method('getProductSku')->willReturn($productSku); + $this->productRepositoryMock + ->expects($this->once()) + ->method('get') + ->with($productSku) + ->willReturn($this->productMock); + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId); + $this->productMock->expects($this->once())->method('getOptions')->willReturn([]); + $this->optionMock->expects($this->once())->method('getData')->with('values')->willReturn([ + ['option_type_id' => 4], + ['option_type_id' => 5] + ]); + $optionCollection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Option\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $optionCollection->expects($this->once())->method('getProductOptions')->willReturn([$this->optionMock]); + $this->optionCollectionFactory->expects($this->once())->method('create')->willReturn($optionCollection); + $this->optionMock->expects($this->once())->method('getValues')->willReturn([ + $originalValue1, + $originalValue2, + $originalValue3 + ]); + $this->assertEquals($this->optionMock, $this->optionRepository->save($this->optionMock)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..222abadd9fe4b9ff2e6c82a9bd7efc24bf5166de --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\Product\Option; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Option; +use \Magento\Catalog\Model\Product\Option\Repository; +use \Magento\Catalog\Model\Product\Option\SaveHandler; + +class SaveHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var SaveHandler|\PHPUnit_Framework_MockObject_MockObject + */ + protected $model; + + /** + * @var Product|\PHPUnit_Framework_MockObject_MockObject + */ + protected $entity; + + /** + * @var Option|\PHPUnit_Framework_MockObject_MockObject + */ + protected $optionMock; + + /** + * @var Repository|\PHPUnit_Framework_MockObject_MockObject + */ + protected $optionRepository; + + public function setUp() + { + $this->entity = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + $this->optionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->getMock(); + $this->optionRepository = $this->getMockBuilder(Repository::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new SaveHandler($this->optionRepository); + } + + public function testExecute() + { + $this->optionMock->expects($this->any())->method('getOptionId')->willReturn(5); + $this->entity->expects($this->once())->method('getOptions')->willReturn([$this->optionMock]); + + $secondOptionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->getMock(); + $secondOptionMock->expects($this->once())->method('getOptionId')->willReturn(6); + + $this->optionRepository + ->expects($this->once()) + ->method('getProductOptions') + ->with($this->entity) + ->willReturn([$this->optionMock, $secondOptionMock]); + + $this->optionRepository->expects($this->once())->method('delete'); + $this->optionRepository->expects($this->once())->method('save')->with($this->optionMock); + + $this->assertEquals($this->entity, $this->model->execute($this->entity)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php index 9b41651863fdb21112d422dd7bda8ee6a97aa09b..6682b295476325f2df099faa183d5edb451cadb5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php @@ -5,6 +5,12 @@ */ namespace Magento\Catalog\Test\Unit\Model\Product\Option\Type; +use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverPool; + class FileTest extends \PHPUnit_Framework_TestCase { /** @@ -13,7 +19,7 @@ class FileTest extends \PHPUnit_Framework_TestCase protected $objectManager; /** - * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $rootDirectory; @@ -22,14 +28,26 @@ class FileTest extends \PHPUnit_Framework_TestCase */ protected $coreFileStorageDatabase; + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystemMock; + protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->rootDirectory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() - ->setMethods(['isFile', 'isReadable', 'getAbsolutePath']) - ->getMockForAbstractClass(); + ->getMock(); + + $this->rootDirectory = $this->getMockBuilder(ReadInterface::class) + ->getMock(); + + $this->filesystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::MEDIA, DriverPool::FILE) + ->willReturn($this->rootDirectory); $this->coreFileStorageDatabase = $this->getMock( \Magento\MediaStorage\Helper\File\Storage\Database::class, @@ -48,26 +66,27 @@ class FileTest extends \PHPUnit_Framework_TestCase return $this->objectManager->getObject( \Magento\Catalog\Model\Product\Option\Type\File::class, [ - 'saleableItem' => $this->rootDirectory, - 'priceCurrency' => $this->coreFileStorageDatabase + 'filesystem' => $this->filesystemMock, + 'coreFileStorageDatabase' => $this->coreFileStorageDatabase ] ); } public function testCopyQuoteToOrder() { - $optionMock = $this->getMockBuilder( - \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface::class - )->disableOriginalConstructor()->setMethods(['getValue'])->getMockForAbstractClass(); + $optionMock = $this->getMockBuilder(OptionInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getValue']) + ->getMockForAbstractClass(); $quotePath = '/quote/path/path/uploaded.file'; $orderPath = '/order/path/path/uploaded.file'; $optionMock->expects($this->any()) ->method('getValue') - ->will($this->returnValue(['quote_path' => $quotePath, 'order_path' => $orderPath])); + ->will($this->returnValue(serialize(['quote_path' => $quotePath, 'order_path' => $orderPath]))); - $this->rootDirectory->expects($this->any()) + $this->rootDirectory->expects($this->once()) ->method('isFile') ->with($this->equalTo($quotePath)) ->will($this->returnValue(true)); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index dd858fa571ae1d10ce6021f7f257579bc803cf5a..f8e162dff45f390156e63c779ce2c2818d723aa5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -708,13 +708,13 @@ class ProductTest extends \PHPUnit_Framework_TestCase return [ 'no changes' => [ - ['catalog_product_1'], + ['cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1]], ['id' => 1, 'name' => 'value', 'category_ids' => [1]], ], 'new product' => $this->getNewProductProviderData(), 'status and category change' => [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_1', 2 => 'catalog_category_product_2'], + [0 => 'cat_p_1', 1 => 'cat_c_p_1', 2 => 'cat_c_p_2'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 2], [ 'id' => 1, @@ -726,18 +726,18 @@ class ProductTest extends \PHPUnit_Framework_TestCase ], ], 'status change only' => [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_7'], + [0 => 'cat_p_1', 1 => 'cat_c_p_7'], ['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => 1], ['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => 2], ], 'status changed, category unassigned' => $this->getStatusAndCategoryChangesData(), 'no status changes' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], ], 'no stock status changes' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -749,7 +749,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase ], ], 'no stock status data 1' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -760,7 +760,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase ], ], 'no stock status data 2' => [ - [0 => 'catalog_product_1'], + [0 => 'cat_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -780,7 +780,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase private function getStatusAndCategoryChangesData() { return [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_5'], + [0 => 'cat_p_1', 1 => 'cat_c_p_5'], ['id' => 1, 'name' => 'value', 'category_ids' => [5], 'status' => 2], [ 'id' => 1, @@ -799,7 +799,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase private function getNewProductProviderData() { return [ - ['catalog_product_1', 'catalog_category_product_1'], + ['cat_p_1', 'cat_c_p_1'], null, [ 'id' => 1, @@ -818,7 +818,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase private function getStatusStockProviderData($extensionAttributesMock) { return [ - [0 => 'catalog_product_1', 1 => 'catalog_category_product_1'], + [0 => 'cat_p_1', 1 => 'cat_c_p_1'], ['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => 1], [ 'id' => 1, @@ -831,20 +831,6 @@ class ProductTest extends \PHPUnit_Framework_TestCase ]; } - public function testStatusAfterLoad() - { - $this->resource->expects($this->once())->method('load')->with($this->model, 1, null); - $this->eventManagerMock->expects($this->exactly(4))->method('dispatch'); - $this->model->load(1); - $this->assertEquals( - Status::STATUS_ENABLED, - $this->model->getData(\Magento\Catalog\Model\Product::STATUS) - ); - $this->assertFalse($this->model->hasDataChanges()); - $this->model->setStatus(Status::STATUS_DISABLED); - $this->assertTrue($this->model->hasDataChanges()); - } - /** * Test retrieving price Info */ diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php index c2b3de0c24cb0736b778efb75d9e722d0fcb47b8..75a2b7c27b4d263d6603438ae9d7c8ab13578e91 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CustomOptionsTest.php @@ -87,35 +87,47 @@ class CustomOptionsTest extends AbstractModifierTest $originalData = [ $productId => [ - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::DATA_SOURCE_DEFAULT => [ + CustomOptions::DATA_SOURCE_DEFAULT => [ 'title' => 'original' ] ] ]; $options = [ - $this->getProductOptionMock(['title' => 'option1']), + $this->getProductOptionMock(['title' => 'option1', 'store_title' => 'Option Store Title']), $this->getProductOptionMock( - ['title' => 'option2'], + ['title' => 'option2', 'store_title' => null], [ - $this->getProductOptionMock(['title' => 'value1']), - $this->getProductOptionMock(['title' => 'value2']) + $this->getProductOptionMock(['title' => 'value1', 'store_title' => 'Option Value Store Title']), + $this->getProductOptionMock(['title' => 'value2', 'store_title' => null]) ] ) ]; $resultData = [ $productId => [ - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::DATA_SOURCE_DEFAULT => [ - 'title' => 'original', - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::FIELD_ENABLE => 1, - \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions::GRID_OPTIONS_NAME => [ - ['title' => 'option1'], + CustomOptions::DATA_SOURCE_DEFAULT => [ + CustomOptions::FIELD_TITLE_NAME => 'original', + CustomOptions::FIELD_ENABLE => 1, + CustomOptions::GRID_OPTIONS_NAME => [ [ - 'title' => 'option2', + CustomOptions::FIELD_TITLE_NAME => 'option1', + CustomOptions::FIELD_STORE_TITLE_NAME => 'Option Store Title', + CustomOptions::FIELD_IS_USE_DEFAULT => false + ], [ + CustomOptions::FIELD_TITLE_NAME => 'option2', + CustomOptions::FIELD_STORE_TITLE_NAME => null, + CustomOptions::FIELD_IS_USE_DEFAULT => true, CustomOptions::GRID_TYPE_SELECT_NAME => [ - ['title' => 'value1'], - ['title' => 'value2'] + [ + CustomOptions::FIELD_TITLE_NAME => 'value1', + CustomOptions::FIELD_STORE_TITLE_NAME => 'Option Value Store Title', + CustomOptions::FIELD_IS_USE_DEFAULT => false + ], [ + CustomOptions::FIELD_TITLE_NAME => 'value2', + CustomOptions::FIELD_STORE_TITLE_NAME => null, + CustomOptions::FIELD_IS_USE_DEFAULT => true + ] ] ] ] @@ -154,13 +166,13 @@ class CustomOptionsTest extends AbstractModifierTest */ protected function getProductOptionMock(array $data, array $values = []) { + /** @var ProductOption|\PHPUnit_Framework_MockObject_MockObject $productOptionMock */ $productOptionMock = $this->getMockBuilder(ProductOption::class) ->disableOriginalConstructor() + ->setMethods(['getValues']) ->getMock(); - $productOptionMock->expects($this->any()) - ->method('getData') - ->willReturn($data); + $productOptionMock->setData($data); $productOptionMock->expects($this->any()) ->method('getValues') ->willReturn($values); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 5e6c88a1ba93d43f3ab05236769761aa17fcfb12..780cdbbb00e0a3ac06208e6c70a01708376f071d 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -68,6 +68,7 @@ class CustomOptions extends AbstractModifier const FIELD_ENABLE = 'affect_product_custom_options'; const FIELD_OPTION_ID = 'option_id'; const FIELD_TITLE_NAME = 'title'; + const FIELD_STORE_TITLE_NAME = 'store_title'; const FIELD_TYPE_NAME = 'type'; const FIELD_IS_REQUIRE_NAME = 'is_require'; const FIELD_SORT_ORDER_NAME = 'sort_order'; @@ -79,6 +80,7 @@ class CustomOptions extends AbstractModifier const FIELD_IMAGE_SIZE_X_NAME = 'image_size_x'; const FIELD_IMAGE_SIZE_Y_NAME = 'image_size_y'; const FIELD_IS_DELETE = 'is_delete'; + const FIELD_IS_USE_DEFAULT = 'is_use_default'; /**#@-*/ /**#@+ @@ -112,7 +114,7 @@ class CustomOptions extends AbstractModifier * @var UrlInterface */ protected $urlBuilder; - + /** * @var ArrayManager */ @@ -162,9 +164,14 @@ class CustomOptions extends AbstractModifier /** @var \Magento\Catalog\Model\Product\Option $option */ foreach ($productOptions as $index => $option) { - $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $option->getData()); + $optionData = $option->getData(); + $optionData[static::FIELD_IS_USE_DEFAULT] = !$option->getData(static::FIELD_STORE_TITLE_NAME); + $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $optionData); $values = $option->getValues() ?: []; + foreach ($values as $value) { + $value->setData(static::FIELD_IS_USE_DEFAULT, !$value->getData(static::FIELD_STORE_TITLE_NAME)); + } /** @var \Magento\Catalog\Model\Product\Option $value */ foreach ($values as $value) { $options[$index][static::GRID_TYPE_SELECT_NAME][] = $this->formatPriceByPath( @@ -529,7 +536,8 @@ class CustomOptions extends AbstractModifier 'component' => 'Magento_Catalog/component/static-type-input', 'valueUpdate' => 'input', 'imports' => [ - 'optionId' => '${ $.provider }:${ $.parentScope }.option_id' + 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' ] ], ], @@ -604,6 +612,7 @@ class CustomOptions extends AbstractModifier 'imports' => [ 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', 'optionTypeId' => '${ $.provider }:${ $.parentScope }.option_type_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' ], 'service' => [ 'template' => 'Magento_Catalog/form/element/helper/custom-option-type-service', @@ -1109,7 +1118,7 @@ class CustomOptions extends AbstractModifier } return $this->localeCurrency; } - + /** * Format price according to the locale of the currency * diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index 1419c17e11b298d1fcdc554a3131d7b8c9b8ffe8..e1680ab5a4081946b73b375a85c0b53cf916afd4 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -754,7 +754,10 @@ class Eav extends AbstractModifier $meta['arguments']['data']['config']['wysiwyg'] = true; $meta['arguments']['data']['config']['wysiwygConfigData'] = [ 'add_variables' => false, - 'add_widgets' => false + 'add_widgets' => false, + 'add_directives' => true, + 'use_container' => true, + 'container_class' => 'hor-scroll', ]; return $meta; diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 49ad7d67d705899077d4beb13f4a26383982dcf5..d191f0332f5f28d28f22e981f8ebc74f0c0c8b98 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -47,6 +47,7 @@ <preference for="Magento\Catalog\Api\CategoryListInterface" type="Magento\Catalog\Model\CategoryList" /> <preference for="Magento\Catalog\Api\Data\CategorySearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface" type="Magento\Catalog\Model\Config\Source\Product\Options\Price"/> + <preference for="Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterface" type="Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder"/> <type name="Magento\Customer\Model\ResourceModel\Visitor"> <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" /> </type> @@ -833,4 +834,7 @@ <argument name="collectionProcessor" xsi:type="object">Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor</argument> </arguments> </type> + <type name="Magento\Quote\Model\Quote\Item\ToOrderItem"> + <plugin name="copy_quote_files_to_order" type="Magento\Catalog\Model\Plugin\QuoteItemProductOption"/> + </type> </config> diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 9b99bd4a781402d662fa6e9c7a17d925c28e6460..ac8c3693e8f3056611a9152e7f259a7e547c4311 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -18,9 +18,6 @@ <argument name="fetchStrategy" xsi:type="object">Magento\Catalog\Model\ResourceModel\Category\Collection\FetchStrategy</argument> </arguments> </type> - <type name="Magento\Quote\Model\Quote\Item\ToOrderItem"> - <plugin name="copy_quote_files_to_order" type="Magento\Catalog\Model\Plugin\QuoteItemProductOption"/> - </type> <type name="Magento\Catalog\Model\Indexer\AbstractFlatState"> <arguments> <argument name="isAvailable" xsi:type="boolean">true</argument> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js index fd69874c318e21606361d503cc55ea900ad604cf..e67bde152475f62769d6eaff73548893f98ed7d0 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js @@ -4,8 +4,9 @@ */ define([ + 'underscore', 'Magento_Ui/js/form/element/textarea' -], function (Textarea) { +], function (_, Textarea) { 'use strict'; return Textarea.extend({ @@ -123,24 +124,21 @@ define([ * Update field value, if it's allowed */ updateValue: function () { - var str = this.mask, + var str = this.mask || '', nonEmptyValueFlag = false, - placeholder, - property, tmpElement; if (!this.allowImport) { return; } - for (property in this.values) { - if (this.values.hasOwnProperty(property)) { - placeholder = ''; - placeholder = placeholder.concat('{{', property, '}}'); - str = str.replace(placeholder, this.values[property]); - nonEmptyValueFlag = nonEmptyValueFlag || !!this.values[property]; - } + if (str) { + _.each(this.values, function (propertyValue, propertyName) { + str = str.replace('{{' + propertyName + '}}', propertyValue); + nonEmptyValueFlag = nonEmptyValueFlag || !!propertyValue; + }); } + // strip tags tmpElement = document.createElement('div'); tmpElement.innerHTML = str; diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html index a3f7970e7420ad52ceb96ed23943f8166d1e2c4a..442d96b5ae4bac2c2954f377bba818448d1284b9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/helper/custom-option-type-service.html @@ -12,6 +12,6 @@ name: 'options_use_default[' + $data.optionId + '][values][' + $data.optionTypeId + '][' + $data.index + ']', 'data-form-part': $data.ns " - ko-checked="isUseDefault"> + ko-checked="$data.isUseDefault"> <label translate="'Use Default Value'" attr="for: $data.uid + '_default'" class="admin__field-label"></label> </div> diff --git a/app/code/Magento/Catalog/view/frontend/requirejs-config.js b/app/code/Magento/Catalog/view/frontend/requirejs-config.js index c0d05a322b7cd9e0a0603cdbef8505963ead80fa..6c7a8d3a969fff3090e0c24bbb431eebf83b9e6b 100644 --- a/app/code/Magento/Catalog/view/frontend/requirejs-config.js +++ b/app/code/Magento/Catalog/view/frontend/requirejs-config.js @@ -6,7 +6,6 @@ var config = { map: { '*': { - compareItems: 'Magento_Catalog/js/compare', compareList: 'Magento_Catalog/js/list', relatedProducts: 'Magento_Catalog/js/related-products', upsellProducts: 'Magento_Catalog/js/upsell-products', diff --git a/app/code/Magento/Catalog/view/frontend/web/js/compare.js b/app/code/Magento/Catalog/view/frontend/web/js/compare.js deleted file mode 100644 index 66f8767bf2b8830c4229588d7c0a7ac5a8b6a422..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/view/frontend/web/js/compare.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -/*jshint browser:true jquery:true*/ -/*global confirm:true*/ -define([ - "jquery", - "jquery/ui", - "mage/decorate" -], function($){ - "use strict"; - - $.widget('mage.compareItems', { - _create: function() { - this.element.decorate('list', true); - this._confirm(this.options.removeSelector, this.options.removeConfirmMessage); - this._confirm(this.options.clearAllSelector, this.options.clearAllConfirmMessage); - }, - - /** - * Set up a click event on the given selector to display a confirmation request message - * and ask for that confirmation. - * @param selector Selector for the confirmation on click event - * @param message Message to display asking for confirmation to perform action - * @private - */ - _confirm: function(selector, message) { - $(selector).on('click', function() { - return confirm(message); - }); - } - }); - - return $.mage.compareItems; -}); \ No newline at end of file diff --git a/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js b/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js index 5dba397d9bc43d7e149887fe5519c54469d09b0e..9662b3fc7268b7366b9a448e90ca75690506b13a 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js @@ -2,37 +2,32 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ + define([ 'uiComponent', 'Magento_Customer/js/customer-data', - 'mage/translate' -], function (Component, customerData) { + 'jquery', + 'mage/mage', + 'mage/decorate' +], function (Component, customerData, $) { 'use strict'; var sidebarInitialized = false; + /** + * Initialize sidebar + */ function initSidebar() { if (sidebarInitialized) { return; } - sidebarInitialized = true; - require([ - 'jquery', - 'mage/mage' - ], function ($) { - /*eslint-disable max-len*/ - $('[data-role=compare-products-sidebar]').mage('compareItems', { - 'removeConfirmMessage': $.mage.__('Are you sure you want to remove this item from your Compare Products list?'), - 'removeSelector': '#compare-items a.action.delete', - 'clearAllConfirmMessage': $.mage.__('Are you sure you want to remove all items from your Compare Products list?'), - 'clearAllSelector': '#compare-clear-all' - }); - /*eslint-enable max-len*/ - }); + sidebarInitialized = true; + $('[data-role=compare-products-sidebar]').decorate('list', true); } return Component.extend({ + /** @inheritdoc */ initialize: function () { this._super(); this.compareProducts = customerData.get('compare-products'); diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 2abd8d97ebc01b07bf786173e50969688d4388f1..fa704272962162d43edd1d129c1229275614015a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -127,6 +127,13 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity */ protected $_attributeTypes = []; + /** + * Attributes defined by user + * + * @var array + */ + private $userDefinedAttributes = []; + /** * Product collection * @@ -261,7 +268,11 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity */ protected $dateAttrCodes = [ 'special_from_date', - 'special_to_date' + 'special_to_date', + 'news_from_date', + 'news_to_date', + 'custom_design_from', + 'custom_design_to' ]; /** @@ -838,6 +849,24 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity return $writer->getContents(); } + /** + * {@inheritdoc} + */ + protected function _prepareEntityCollection(\Magento\Eav\Model\Entity\Collection\AbstractCollection $collection) + { + $exportFilter = !empty($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]) ? + $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP] : []; + + if (isset($exportFilter['category_ids']) + && trim($exportFilter['category_ids']) + && $collection instanceof \Magento\Catalog\Model\ResourceModel\Product\Collection + ) { + $collection->addCategoriesFilter(['in' => explode(',', $exportFilter['category_ids'])]); + } + + return parent::_prepareEntityCollection($collection); + } + /** * Get export data for collection * @@ -910,20 +939,24 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity } $fieldName = isset($this->_fieldsMap[$code]) ? $this->_fieldsMap[$code] : $code; - if (in_array($code, $this->dateAttrCodes)) { - $attrValue = $this->_localeDate->formatDateTime( - new \DateTime($attrValue), - \IntlDateFormatter::SHORT, - \IntlDateFormatter::NONE, - null, - date_default_timezone_get() - ); - } else if ($this->_attributeTypes[$code] === 'datetime') { - $attrValue = $this->_localeDate->formatDateTime( - new \DateTime($attrValue), - \IntlDateFormatter::SHORT, - \IntlDateFormatter::SHORT - ); + if ($this->_attributeTypes[$code] == 'datetime') { + if (in_array($code, $this->dateAttrCodes) + || in_array($code, $this->userDefinedAttributes) + ) { + $attrValue = $this->_localeDate->formatDateTime( + new \DateTime($attrValue), + \IntlDateFormatter::SHORT, + \IntlDateFormatter::NONE, + null, + date_default_timezone_get() + ); + } else { + $attrValue = $this->_localeDate->formatDateTime( + new \DateTime($attrValue), + \IntlDateFormatter::SHORT, + \IntlDateFormatter::SHORT + ); + } } if ($storeId != Store::DEFAULT_STORE_ID @@ -937,7 +970,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity if (is_scalar($attrValue)) { if (!in_array($fieldName, $this->_getExportMainAttrCodes())) { $additionalAttributes[$fieldName] = $fieldName . - ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $attrValue; + ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $this->wrapValue($attrValue); } $data[$itemId][$storeId][$fieldName] = htmlspecialchars_decode($attrValue); } @@ -947,7 +980,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity $additionalAttributes[$code] = $fieldName . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . implode( ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, - $this->collectedMultiselectsData[$storeId][$productLinkId][$code] + $this->wrapValue($this->collectedMultiselectsData[$storeId][$productLinkId][$code]) ); } } @@ -978,6 +1011,25 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity return $data; } + /** + * Wrap values with double quotes if "Fields Enclosure" option is enabled + * + * @param string|array $value + * @return string|array + */ + private function wrapValue($value) + { + if (!empty($this->_parameters[\Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE])) { + $wrap = function ($value) { + return sprintf('"%s"', str_replace('"', '""', $value)); + }; + + $value = is_array($value) ? array_map($wrap, $value) : $wrap($value); + } + + return $value; + } + /** * @return array */ @@ -1380,6 +1432,9 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity $this->_attributeValues[$attribute->getAttributeCode()] = $this->getAttributeOptions($attribute); $this->_attributeTypes[$attribute->getAttributeCode()] = \Magento\ImportExport\Model\Import::getAttributeType($attribute); + if ($attribute->getIsUserDefined()) { + $this->userDefinedAttributes[] = $attribute->getAttributeCode(); + } } return $this; } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index b37ba530845e579cd5b91970e4255eee1745696d..afb2e86b6248a79523405c78acd1cf27953b90c2 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -217,7 +217,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ protected $dateAttrCodes = [ 'special_from_date', - 'special_to_date' + 'special_to_date', + 'news_from_date', + 'news_to_date', + 'custom_design_from', + 'custom_design_to' ]; /** @@ -273,7 +277,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity ValidatorInterface::ERROR_MEDIA_PATH_NOT_ACCESSIBLE => 'Imported resource (image) does not exist in the local media storage', ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE => 'Imported resource (image) could not be downloaded from external resource due to timeout or access permissions', ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid', - ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Specified URL key already exists', + ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually' ]; /** @@ -640,6 +644,13 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $productEntityIdentifierField; + /** + * Escaped separator value for regular expression. + * The value is based on PSEUDO_MULTI_LINE_SEPARATOR constant. + * @var string + */ + private $multiLineSeparatorForRegexp; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -1699,7 +1710,10 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity if ( 'datetime' == $attribute->getBackendType() - && in_array($attribute->getAttributeCode(), $this->dateAttrCodes) + && ( + in_array($attribute->getAttributeCode(), $this->dateAttrCodes) + || $attribute->getIsUserDefined() + ) ) { $attrValue = $this->dateTime->formatDate($attrValue, false); } else if ('datetime' == $attribute->getBackendType() && strtotime($attrValue)) { @@ -2367,7 +2381,17 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $this->urlKeys[$storeId][$urlPath] = $rowData[self::COL_SKU]; $this->rowNumbers[$storeId][$urlPath] = $rowNum; } else { - $this->addRowError(ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum); + $message = sprintf( + $this->retrieveMessageTemplate(ValidatorInterface::ERROR_DUPLICATE_URL_KEY), + $urlKey, + $this->urlKeys[$storeId][$urlPath] + ); + $this->addRowError( + ValidatorInterface::ERROR_DUPLICATE_URL_KEY, + $rowNum, + $rowData[self::COL_NAME], + $message + ); } } } @@ -2421,14 +2445,40 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity } /** - * Retrieves additional attributes as array code=>value. + * Retrieves additional attributes in format: + * [ + * code1 => value1, + * code2 => value2, + * ... + * codeN => valueN + * ] * - * @param string $additionalAttributes + * @param string $additionalAttributes Attributes data that will be parsed * @return array */ private function parseAdditionalAttributes($additionalAttributes) { - $attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $additionalAttributes); + return empty($this->_parameters[Import::FIELDS_ENCLOSURE]) + ? $this->parseAttributesWithoutWrappedValues($additionalAttributes) + : $this->parseAttributesWithWrappedValues($additionalAttributes); + } + + /** + * Parses data and returns attributes in format: + * [ + * code1 => value1, + * code2 => value2, + * ... + * codeN => valueN + * ] + * + * @param string $attributesData Attributes data that will be parsed. It keeps data in format: + * code=value,code2=value2...,codeN=valueN + * @return array + */ + private function parseAttributesWithoutWrappedValues($attributesData) + { + $attributeNameValuePairs = explode($this->getMultipleValueSeparator(), $attributesData); $preparedAttributes = []; $code = ''; foreach ($attributeNameValuePairs as $attributeData) { @@ -2446,6 +2496,75 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity return $preparedAttributes; } + /** + * Parses data and returns attributes in format: + * [ + * code1 => value1, + * code2 => value2, + * ... + * codeN => valueN + * ] + * All values have unescaped data except mupliselect attributes, + * they should be parsed in additional method - parseMultiselectValues() + * + * @param string $attributesData Attributes data that will be parsed. It keeps data in format: + * code="value",code2="value2"...,codeN="valueN" + * where every value is wrapped in double quotes. Double quotes as part of value should be duplicated. + * E.g. attribute with code 'attr_code' has value 'my"value'. This data should be stored as attr_code="my""value" + * + * @return array + */ + private function parseAttributesWithWrappedValues($attributesData) + { + $attributes = []; + preg_match_all('~((?:[a-z0-9_])+)="((?:[^"]|""|"' . $this->getMultiLineSeparatorForRegexp() . '")+)"+~', + $attributesData, + $matches + ); + foreach ($matches[1] as $i => $attributeCode) { + $attribute = $this->retrieveAttributeByCode($attributeCode); + $value = 'multiselect' != $attribute->getFrontendInput() + ? str_replace('""', '"', $matches[2][$i]) + : '"' . $matches[2][$i] . '"'; + $attributes[$attributeCode] = $value; + } + return $attributes; + } + + /** + * Parse values of multiselect attributes depends on "Fields Enclosure" parameter + * + * @param string $values + * @return array + */ + public function parseMultiselectValues($values) + { + if (empty($this->_parameters[Import::FIELDS_ENCLOSURE])) { + return explode(self::PSEUDO_MULTI_LINE_SEPARATOR, $values); + } + if (preg_match_all('~"((?:[^"]|"")*)"~', $values, $matches)) { + return $values = array_map(function ($value) { + return str_replace('""', '"', $value); + }, $matches[1]); + } + return [$values]; + } + + /** + * Retrieves escaped PSEUDO_MULTI_LINE_SEPARATOR if it is metacharacter for regular expression + * + * @return string + */ + private function getMultiLineSeparatorForRegexp() + { + if (!$this->multiLineSeparatorForRegexp) { + $this->multiLineSeparatorForRegexp = in_array(self::PSEUDO_MULTI_LINE_SEPARATOR, str_split('[\^$.|?*+(){}')) + ? '\\' . self::PSEUDO_MULTI_LINE_SEPARATOR + : self::PSEUDO_MULTI_LINE_SEPARATOR; + } + return $this->multiLineSeparatorForRegexp; + } + /** * Set values in use_config_ fields. * @@ -2523,7 +2642,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $rowData = $this->_customFieldsMapping($rowData); $this->validateRow($rowData, $source->key()); - + $source->next(); } $this->checkUrlKeyDuplicates(); diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php index 8461e617431d64fe3529162d54841d7da4740cdf..0d19b9fcbedc85e4df47ab745759282f4824b060 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php @@ -490,23 +490,25 @@ abstract class AbstractType $resultAttrs = []; foreach ($this->_getProductAttributes($rowData) as $attrCode => $attrParams) { - if (!$attrParams['is_static']) { - if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) { - $resultAttrs[$attrCode] = in_array($attrParams['type'], ['select', 'boolean']) - ? $attrParams['options'][strtolower($rowData[$attrCode])] - : $rowData[$attrCode]; - if ('multiselect' == $attrParams['type']) { - $resultAttrs[$attrCode] = []; - foreach (explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData[$attrCode]) as $value) { - $resultAttrs[$attrCode][] = $attrParams['options'][strtolower($value)]; - } - $resultAttrs[$attrCode] = implode(',', $resultAttrs[$attrCode]); + if ($attrParams['is_static']) { + continue; + } + if (isset($rowData[$attrCode]) && strlen($rowData[$attrCode])) { + if (in_array($attrParams['type'], ['select', 'boolean'])) { + $resultAttrs[$attrCode] = $attrParams['options'][strtolower($rowData[$attrCode])]; + } elseif ('multiselect' == $attrParams['type']) { + $resultAttrs[$attrCode] = []; + foreach ($this->_entityModel->parseMultiselectValues($rowData[$attrCode]) as $value) { + $resultAttrs[$attrCode][] = $attrParams['options'][strtolower($value)]; } - } elseif (array_key_exists($attrCode, $rowData)) { + $resultAttrs[$attrCode] = implode(',', $resultAttrs[$attrCode]); + } else { $resultAttrs[$attrCode] = $rowData[$attrCode]; - } elseif ($withDefaultValue && null !== $attrParams['default_value']) { - $resultAttrs[$attrCode] = $attrParams['default_value']; } + } elseif (array_key_exists($attrCode, $rowData)) { + $resultAttrs[$attrCode] = $rowData[$attrCode]; + } elseif ($withDefaultValue && null !== $attrParams['default_value']) { + $resultAttrs[$attrCode] = $attrParams['default_value']; } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php index e57983796f695fcac97325123d102cbe5a9854b7..0ca40e916c85b0fe50886f36fe502538a370ae50 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php @@ -71,6 +71,32 @@ class Validator extends AbstractValidator implements RowValidatorInterface return $valid; } + /** + * Check if value is valid attribute option + * + * @param string $attrCode + * @param array $possibleOptions + * @param string $value + * @return bool + */ + private function validateOption($attrCode, $possibleOptions, $value) + { + if (!isset($possibleOptions[strtolower($value)])) { + $this->_addMessages( + [ + sprintf( + $this->context->retrieveMessageTemplate( + RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION + ), + $attrCode + ) + ] + ); + return false; + } + return true; + } + /** * @param mixed $attrCode * @param string $type @@ -166,23 +192,15 @@ class Validator extends AbstractValidator implements RowValidatorInterface break; case 'select': case 'boolean': + $valid = $this->validateOption($attrCode, $attrParams['options'], $rowData[$attrCode]); + break; case 'multiselect': - $values = explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData[$attrCode]); - $valid = true; + $values = $this->context->parseMultiselectValues($rowData[$attrCode]); foreach ($values as $value) { - $valid = $valid && isset($attrParams['options'][strtolower($value)]); - } - if (!$valid) { - $this->_addMessages( - [ - sprintf( - $this->context->retrieveMessageTemplate( - RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION - ), - $attrCode - ) - ] - ); + $valid = $this->validateOption($attrCode, $attrParams['options'], $value); + if (!$valid) { + break; + } } break; case 'datetime': diff --git a/app/code/Magento/CatalogInventory/Helper/Stock.php b/app/code/Magento/CatalogInventory/Helper/Stock.php index 6169efca64bd08e06181bddbf37f06e6e0648dd1..4c177c09d4b79a62f2a5b07c7b208c37d1c05ec3 100644 --- a/app/code/Magento/CatalogInventory/Helper/Stock.php +++ b/app/code/Magento/CatalogInventory/Helper/Stock.php @@ -154,7 +154,10 @@ class Stock \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); $resource = $this->getStockStatusResource(); - $resource->addStockDataToCollection($collection, !$isShowOutOfStock); + $resource->addStockDataToCollection( + $collection, + !$isShowOutOfStock && $collection->getFlag('require_stock_items') + ); $collection->setFlag($stockFlag, true); } } diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php index c5cdd1950cc3803a238bdd77caa1ee5fa2f8b88e..58e364920bb6ad4919212ba329e2d8294cd36027 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php @@ -15,13 +15,14 @@ use Magento\CatalogInventory\Model\Indexer\Stock\Processor; use Magento\CatalogInventory\Model\ResourceModel\Stock\Item as StockItemResource; use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface; use Magento\CatalogInventory\Model\StockRegistryStorage; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\MapperFactory; use Magento\Framework\DB\QueryBuilderFactory; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** * Class StockItemRepository @@ -89,6 +90,9 @@ class StockItemRepository implements StockItemRepositoryInterface */ protected $stockRegistryStorage; + /** @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory */ + protected $productCollectionFactory; + /** * @param StockConfigurationInterface $stockConfiguration * @param StockStateProviderInterface $stockStateProvider @@ -129,6 +133,21 @@ class StockItemRepository implements StockItemRepositoryInterface $this->dateTime = $dateTime; } + /** + * @deprecated + * @return \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory + */ + private function getProductCollectionFactory() + { + if ($this->productCollectionFactory === null) { + $this->productCollectionFactory = ObjectManager::getInstance()->get( + \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class + ); + } + + return $this->productCollectionFactory; + } + /** * @inheritdoc */ @@ -136,8 +155,12 @@ class StockItemRepository implements StockItemRepositoryInterface { try { /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productFactory->create(); - $product->load($stockItem->getProductId()); + $product = $this->getProductCollectionFactory()->create() + ->setFlag('has_stock_status_filter') + ->addIdFilter($stockItem->getProductId()) + ->addFieldToSelect('type_id') + ->getFirstItem(); + if (!$product->getId()) { return $stockItem; } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Adminhtml/Stock/ItemTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Adminhtml/Stock/ItemTest.php index aef8141e70f340862e7c8f610cdfbf6cd4d6cf8d..dfb8073e718a7f31dd316a88efc7bf9b373dcd60 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Adminhtml/Stock/ItemTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Adminhtml/Stock/ItemTest.php @@ -62,6 +62,6 @@ class ItemTest extends \PHPUnit_Framework_TestCase public function testGetIdentities() { $this->_model->setProductId(1); - $this->assertEquals(['catalog_product_1'], $this->_model->getIdentities()); + $this->assertEquals(['cat_p_1'], $this->_model->getIdentities()); } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php index 769e2db4cc84553a3a10e081e6785a02a72b5d15..23b7805e622e32938a3403e58af7f30d8a8d80e8 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/StockItemRepositoryTest.php @@ -5,6 +5,8 @@ */ namespace Magento\CatalogInventory\Test\Unit\Model\Stock; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\CatalogInventory\Model\Stock\StockItemRepository; use Magento\CatalogInventory\Api\Data as InventoryApiData; use Magento\CatalogInventory\Model\StockRegistryStorage; @@ -153,9 +155,8 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['load', 'getId', 'getTypeId', '__wakeup']) ->getMock(); - $this->productFactoryMock->expects($this->any()) - ->method('create') - ->willReturn($this->productMock); + + $this->productFactoryMock->expects($this->any())->method('create')->willReturn($this->productMock); $this->queryBuilderFactoryMock = $this->getMockBuilder(\Magento\Framework\DB\QueryBuilderFactory::class) ->setMethods(['create']) @@ -185,6 +186,22 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); + $productCollection = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Collection::class + )->disableOriginalConstructor()->getMock(); + + $productCollection->expects($this->any())->method('setFlag')->willReturnSelf(); + $productCollection->expects($this->any())->method('addIdFilter')->willReturnSelf(); + $productCollection->expects($this->any())->method('addFieldToSelect')->willReturnSelf(); + $productCollection->expects($this->any())->method('getFirstItem')->willReturn($this->productMock); + + $productCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $productCollectionFactory->expects($this->any())->method('create')->willReturn($productCollection); + $this->model = (new ObjectManager($this))->getObject( StockItemRepository::class, [ @@ -200,6 +217,7 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase 'indexProcessor' => $this->indexProcessorMock, 'dateTime' => $this->dateTime, 'stockRegistryStorage' => $this->stockRegistryStorage, + 'productCollectionFactory' => $productCollectionFactory, ] ); } @@ -263,7 +281,6 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $productId = 1; $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); - $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn($productId); $this->productMock->expects($this->once())->method('getTypeId')->willReturn('typeId'); $this->stockConfigurationMock->expects($this->once())->method('isQty')->with('typeId')->willReturn(true); @@ -309,7 +326,6 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $productId = 1; $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); - $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn(null); $this->stockRegistryStorage->expects($this->never())->method('removeStockItem'); $this->stockRegistryStorage->expects($this->never())->method('removeStockStatus'); @@ -325,7 +341,6 @@ class StockItemRepositoryTest extends \PHPUnit_Framework_TestCase $productId = 1; $this->stockItemMock->expects($this->any())->method('getProductId')->willReturn($productId); - $this->productMock->expects($this->once())->method('load')->with($productId)->willReturnSelf(); $this->productMock->expects($this->once())->method('getId')->willReturn($productId); $this->productMock->expects($this->once())->method('getTypeId')->willReturn('typeId'); $this->stockConfigurationMock->expects($this->once())->method('isQty')->with('typeId')->willReturn(false); diff --git a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml index af60e0247169bb402d5b6d71be4576ad2ed24566..acfd1efa605c758750d1205662cbdb959e655d15 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml +++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml @@ -149,7 +149,7 @@ <item name="source" xsi:type="string">catalog_rule</item> <item name="dataScope" xsi:type="string">customer_group_ids</item> </item> - <item name="options" xsi:type="object">\Magento\Customer\Model\Customer\Source\GroupSourceInterface</item> + <item name="options" xsi:type="object">\Magento\CatalogRule\Model\Rule\CustomerGroupsOptionsProvider</item> </argument> </field> <field name="from_date"> diff --git a/app/code/Magento/Cms/Model/Block.php b/app/code/Magento/Cms/Model/Block.php index d87f0f0d18884b6c31672fc5fdae32cd47e99c22..b25d433b0fee4f96618a31111940f9d410d9f006 100644 --- a/app/code/Magento/Cms/Model/Block.php +++ b/app/code/Magento/Cms/Model/Block.php @@ -23,7 +23,7 @@ class Block extends AbstractModel implements BlockInterface, IdentityInterface /** * CMS block cache tag */ - const CACHE_TAG = 'cms_block'; + const CACHE_TAG = 'cms_b'; /**#@+ * Block's statuses @@ -36,7 +36,7 @@ class Block extends AbstractModel implements BlockInterface, IdentityInterface /** * @var string */ - protected $_cacheTag = 'cms_block'; + protected $_cacheTag = self::CACHE_TAG; /** * Prefix of model events names diff --git a/app/code/Magento/Cms/Model/Page.php b/app/code/Magento/Cms/Model/Page.php index 20c2e0547193160643feddf1ddded16208e74a57..f98b56d82db03660e8a81353332a089f6739d37d 100644 --- a/app/code/Magento/Cms/Model/Page.php +++ b/app/code/Magento/Cms/Model/Page.php @@ -39,12 +39,12 @@ class Page extends AbstractModel implements PageInterface, IdentityInterface /** * CMS page cache tag */ - const CACHE_TAG = 'cms_page'; + const CACHE_TAG = 'cms_p'; /** * @var string */ - protected $_cacheTag = 'cms_page'; + protected $_cacheTag = self::CACHE_TAG; /** * Prefix of model events names diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php index ea24235b0fe2ef694e2087f7a8bdbf4c8ece3399..cb9a6b534609f7205f2c8a569b022389e13436b7 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurations.php @@ -32,7 +32,7 @@ class UpdateConfigurations 'swatch_image', 'small_image', 'thumbnail', - 'image' + 'image', ]; /** @@ -65,13 +65,15 @@ class UpdateConfigurations ) { $configurations = $this->getConfigurations(); $configurations = $this->variationHandler->duplicateImagesForVariations($configurations); - foreach ($configurations as $productId => $productData) { - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productRepository->getById($productId, false, $this->request->getParam('store', 0)); - $productData = $this->variationHandler->processMediaGallery($product, $productData); - $product->addData($productData); - if ($product->hasDataChanges()) { - $product->save(); + if (count($configurations)) { + foreach ($configurations as $productId => $productData) { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->productRepository->getById($productId, false, $this->request->getParam('store', 0)); + $productData = $this->variationHandler->processMediaGallery($product, $productData); + $product->addData($productData); + if ($product->hasDataChanges()) { + $product->save(); + } } } return $configurableProduct; @@ -91,6 +93,12 @@ class UpdateConfigurations } foreach ($configurableMatrix as $item) { + if (empty($item['was_changed'])) { + continue; + } else { + unset($item['was_changed']); + } + if (!$item['newProduct']) { $result[$item['id']] = $this->mapData($item); diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 321870b96d32abf13c48fdc0975efc9ba5248f17..0d0bba60a7777c75deef4bc9e0a41720b9597163 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -29,6 +29,9 @@ class VariationHandler /** @var \Magento\Catalog\Model\ProductFactory */ protected $productFactory; + /** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[] */ + private $attributes; + /** * @var \Magento\CatalogInventory\Api\StockConfigurationInterface * @deprecated @@ -70,6 +73,7 @@ class VariationHandler public function generateSimpleProducts($parentProduct, $productsData) { $generatedProductIds = []; + $this->attributes = null; $productsData = $this->duplicateImagesForVariations($productsData); foreach ($productsData as $simpleProductData) { $newSimpleProduct = $this->productFactory->create(); @@ -160,7 +164,10 @@ class VariationHandler $parentProduct->getNewVariationsAttributeSetId() ); - foreach ($product->getTypeInstance()->getSetAttributes($product) as $attribute) { + if ($this->attributes === null) { + $this->attributes = $product->getTypeInstance()->getSetAttributes($product); + } + foreach ($this->attributes as $attribute) { if ($attribute->getIsUnique() || $attribute->getAttributeCode() == 'url_key' || $attribute->getFrontend()->getInputType() == 'gallery' || diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php index bed5c96b5216eb76344e9ce593ea56056be9a35b..def49f42fa960b738fe952be4a2d18bada61ffd2 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php @@ -90,13 +90,24 @@ class UpdateConfigurationsTest extends \PHPUnit_Framework_TestCase 'swatch_image' => 'simple2_swatch_image', 'small_image' => 'simple2_small_image', 'thumbnail' => 'simple2_thumbnail', - 'image' => 'simple2_image' + 'image' => 'simple2_image', + 'was_changed' => true, ], [ 'newProduct' => false, 'id' => 'product3', - 'qty' => '3' - ] + 'qty' => '3', + 'was_changed' => true, + ], + [ + 'newProduct' => false, + 'id' => 'product4', + 'status' => 'simple4_status', + 'sku' => 'simple2_sku', + 'name' => 'simple2_name', + 'price' => '3.33', + 'weight' => '5.55', + ], ]; $configurations = [ 'product2' => [ @@ -118,8 +129,8 @@ class UpdateConfigurationsTest extends \PHPUnit_Framework_TestCase ]; /** @var Product[]|\PHPUnit_Framework_MockObject_MockObject[] $productMocks */ $productMocks = [ - 'product2' => $this->getProductMock($configurations['product2'], true), - 'product3' => $this->getProductMock($configurations['product3']) + 'product2' => $this->getProductMock($configurations['product2'], true, true), + 'product3' => $this->getProductMock($configurations['product3'], false, true), ]; $this->requestMock->expects(static::any()) @@ -161,26 +172,27 @@ class UpdateConfigurationsTest extends \PHPUnit_Framework_TestCase * @param bool $hasDataChanges * @return Product|\PHPUnit_Framework_MockObject_MockObject */ - protected function getProductMock(array $expectedData = null, $hasDataChanges = false) + protected function getProductMock(array $expectedData = null, $hasDataChanges = false, $wasChanged = false) { $productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->getMock(); - if ($expectedData !== null) { - $productMock->expects(static::once()) - ->method('addData') - ->with($expectedData) + if ($wasChanged !== false) { + if ($expectedData !== null) { + $productMock->expects(static::once()) + ->method('addData') + ->with($expectedData) + ->willReturnSelf(); + } + + $productMock->expects(static::any()) + ->method('hasDataChanges') + ->willReturn($hasDataChanges); + $productMock->expects($hasDataChanges ? static::once() : static::never()) + ->method('save') ->willReturnSelf(); } - - $productMock->expects(static::any()) - ->method('hasDataChanges') - ->willReturn($hasDataChanges); - $productMock->expects($hasDataChanges ? static::once() : static::never()) - ->method('save') - ->willReturnSelf(); - return $productMock; } } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js index ffabd9a8627dfca82da9b87ebd84c799086d9edf..c182d9f8216c09a42868eb487dfa06b0e718b5c7 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js @@ -391,11 +391,11 @@ define([ 'small_image': row['small_image'], image: row.image, 'thumbnail': row.thumbnail, - 'attributes': attributesText + 'attributes': attributesText, + 'was_changed': true }; product[this.canEditField] = row.editable; product[this.newProductField] = row.newProduct; - tmpArray.push(product); }, this); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php index 45d31dc0e2764c4c41888a23ef0bb7f99836fed3..3c3fa73087e4a17a4c883b370c3a3fd44f7ad645 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\SecurityViolationException; class ResetPassword extends \Magento\Customer\Controller\Adminhtml\Index { @@ -40,6 +41,8 @@ class ResetPassword extends \Magento\Customer\Controller\Adminhtml\Index $messages = $exception->getMessage(); } $this->_addSessionErrorMessages($messages); + } catch (SecurityViolationException $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } catch (\Exception $exception) { $this->messageManager->addException( $exception, diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php index b7295fb9ceb4f19dd29fa8fe148552ebb20648e6..44444935973d43e9424242a9931b93121f314bae 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -143,7 +143,7 @@ class ResetPasswordTest extends \PHPUnit_Framework_TestCase $this->messageManager = $this->getMockBuilder( \Magento\Framework\Message\Manager::class )->disableOriginalConstructor()->setMethods( - ['addSuccess', 'addMessage', 'addException'] + ['addSuccess', 'addMessage', 'addException', 'addErrorMessage'] )->getMock(); $this->resultRedirectFactoryMock = $this->getMockBuilder( @@ -332,6 +332,56 @@ class ResetPasswordTest extends \PHPUnit_Framework_TestCase $this->_testedObject->execute(); } + public function testResetPasswordActionSecurityException() + { + $securityText = 'Security violation.'; + $exception = new \Magento\Framework\Exception\SecurityViolationException(__($securityText)); + $customerId = 1; + $email = 'some@example.com'; + $websiteId = 1; + + $this->_request->expects( + $this->once() + )->method( + 'getParam' + )->with( + $this->equalTo('customer_id'), + $this->equalTo(0) + )->will( + $this->returnValue($customerId) + ); + $customer = $this->getMockForAbstractClass( + \Magento\Customer\Api\Data\CustomerInterface::class, + ['getId', 'getEmail', 'getWebsiteId'] + ); + $customer->expects($this->once())->method('getEmail')->will($this->returnValue($email)); + $customer->expects($this->once())->method('getWebsiteId')->will($this->returnValue($websiteId)); + $this->_customerRepositoryMock->expects( + $this->once() + )->method( + 'getById' + )->with( + $customerId + )->will( + $this->returnValue($customer) + ); + $this->_customerAccountManagementMock->expects( + $this->once() + )->method( + 'initiatePasswordReset' + )->willThrowException($exception); + + $this->messageManager->expects( + $this->once() + )->method( + 'addErrorMessage' + )->with( + $this->equalTo($exception->getMessage()) + ); + + $this->_testedObject->execute(); + } + public function testResetPasswordActionCoreExceptionWarn() { $warningText = 'Warning'; diff --git a/app/code/Magento/Customer/view/frontend/requirejs-config.js b/app/code/Magento/Customer/view/frontend/requirejs-config.js index 53f12373bfd928a1fcea86ee50ba4038caf0cf6a..508737742cafe2e2cf9905571ae3655bba440ad5 100644 --- a/app/code/Magento/Customer/view/frontend/requirejs-config.js +++ b/app/code/Magento/Customer/view/frontend/requirejs-config.js @@ -10,7 +10,8 @@ var config = { address: 'Magento_Customer/address', changeEmailPassword: 'Magento_Customer/change-email-password', passwordStrengthIndicator: 'Magento_Customer/js/password-strength-indicator', - zxcvbn: 'Magento_Customer/js/zxcvbn' + zxcvbn: 'Magento_Customer/js/zxcvbn', + addressValidation: 'Magento_Customer/js/addressValidation' } } }; diff --git a/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml b/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml index 2de971bf6a9c05d1c07cb5745272fcad7a135e23..17d7c9852fc4b5e0efa4ba8342a9bcff0d97d805 100644 --- a/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml @@ -203,7 +203,7 @@ <script type="text/x-magento-init"> { "#form-validate": { - "validation": {} + "addressValidation": {} }, "#country": { "regionUpdater": { diff --git a/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js b/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js new file mode 100644 index 0000000000000000000000000000000000000000..c01463b4046aebc4277fcaf6d8765d1d7a785893 --- /dev/null +++ b/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js @@ -0,0 +1,43 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*jshint browser:true jquery:true*/ +define([ + 'jquery', + 'jquery/ui', + 'validation' +], function ($) { + 'use strict'; + + $.widget('mage.addressValidation', { + options: { + selectors: { + button: '[data-action=save-address]' + } + }, + + /** + * Validation creation + * @protected + */ + _create: function () { + var button = $(this.options.selectors.button, this.element); + + this.element.validation({ + + /** + * Submit Handler + * @param {Element} form - address form + */ + submitHandler: function (form) { + + button.attr('disabled', true); + form.submit(); + } + }); + } + }); + + return $.mage.addressValidation; +}); diff --git a/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php b/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php index 51345c2b7609cb8e39cf25fb9a4f4f202cba6f65..8a006c8154b4b97eb6d39c1d41f8363b3e4df761 100644 --- a/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php +++ b/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php @@ -3,7 +3,6 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Deploy\Console\Command; use Magento\Framework\App\Utility\Files; @@ -19,6 +18,8 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\State; use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; use Magento\Deploy\Model\DeployManager; +use Magento\Framework\App\Cache; +use Magento\Framework\App\Cache\Type\Dummy as DummyCache; /** * Deploy static content command @@ -29,7 +30,7 @@ class DeployStaticContentCommand extends Command /** * Key for dry-run option * @deprecated - * @see Magento\Deploy\Console\Command\DeployStaticOptionsInterface::DRY_RUN + * @see \Magento\Deploy\Console\Command\DeployStaticOptionsInterface::DRY_RUN */ const DRY_RUN_OPTION = 'dry-run'; @@ -87,6 +88,7 @@ class DeployStaticContentCommand extends Command * @param ObjectManagerFactory $objectManagerFactory * @param Locale $validator * @param ObjectManagerInterface $objectManager + * @throws \LogicException When the command name is empty */ public function __construct( ObjectManagerFactory $objectManagerFactory, @@ -96,6 +98,7 @@ class DeployStaticContentCommand extends Command $this->objectManagerFactory = $objectManagerFactory; $this->validator = $validator; $this->objectManager = $objectManager; + parent::__construct(); } @@ -373,6 +376,7 @@ class DeployStaticContentCommand extends Command /** * {@inheritdoc} * @throws \InvalidArgumentException + * @throws LocalizedException */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -394,9 +398,9 @@ class DeployStaticContentCommand extends Command list ($deployableLanguages, $deployableAreaThemeMap, $requestedThemes) = $this->prepareDeployableEntities($filesUtil); - $output->writeln("Requested languages: " . implode(', ', $deployableLanguages)); - $output->writeln("Requested areas: " . implode(', ', array_keys($deployableAreaThemeMap))); - $output->writeln("Requested themes: " . implode(', ', $requestedThemes)); + $output->writeln('Requested languages: ' . implode(', ', $deployableLanguages)); + $output->writeln('Requested areas: ' . implode(', ', array_keys($deployableAreaThemeMap))); + $output->writeln('Requested themes: ' . implode(', ', $requestedThemes)); /** @var $deployManager DeployManager */ $deployManager = $this->objectManager->create( @@ -415,11 +419,13 @@ class DeployStaticContentCommand extends Command } } + $this->mockCache(); return $deployManager->deploy(); } /** * @param Files $filesUtil + * @throws \InvalidArgumentException * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -476,4 +482,18 @@ class DeployStaticContentCommand extends Command return [$deployableLanguages, $deployableAreaThemeMap, $requestedThemes]; } + + /** + * Mock Cache class with dummy implementation + * + * @return void + */ + private function mockCache() + { + $this->objectManager->configure([ + 'preferences' => [ + Cache::class => DummyCache::class + ] + ]); + } } diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index c3e5a4b0c4d462b2f7e6576e7c8666e71b18be58..5a88c601e17e3c033040b082f8dfe47b83fd6a03 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -13,7 +13,8 @@ use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend; use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; use Magento\Framework\App\Config\Element; -use Magento\Framework\App\ResourceConnection\Config; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor; @@ -950,6 +951,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Load object base row data */ + $object->beforeLoad($entityId); $select = $this->_getLoadRowSelect($object, $entityId); $row = $this->getConnection()->fetchRow($select); @@ -962,11 +964,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac $this->loadAttributesMetadata($attributes); $this->_loadModelAttributes($object); - - $object->setOrigData(); - $this->_afterLoad($object); - + $object->afterLoad(); + $object->setOrigData(); + $object->setHasDataChanges(false); \Magento\Framework\Profiler::stop('EAV:load_entity'); return $this; } @@ -1108,6 +1109,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac * @param \Magento\Framework\Model\AbstractModel $object * @return $this * @throws \Exception + * @throws AlreadyExistsException */ public function save(\Magento\Framework\Model\AbstractModel $object) { @@ -1147,6 +1149,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac } $this->addCommitCallback([$object, 'afterCommitCallback'])->commit(); $object->setHasDataChanges(false); + } catch (DuplicateException $e) { + $this->rollBack(); + $object->setHasDataChanges(true); + throw new AlreadyExistsException(__('Unique constraint violation found'), $e); } catch (\Exception $e) { $this->rollBack(); $object->setHasDataChanges(true); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index 4f13ee75bfe32c29ae544c19e950691ead763323..0d8d18df223767b2812f58356de3b7904090d8f7 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -6,8 +6,8 @@ namespace Magento\Eav\Model\Entity\Attribute; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Api\AttributeValueFactory; +use Magento\Framework\Exception\LocalizedException; /** * Entity/Attribute/Model - attribute abstract @@ -595,11 +595,10 @@ abstract class AbstractAttribute extends \Magento\Framework\Model\AbstractExtens { /** @var array $emptyStringTypes list of attribute types that treat empty string as a possible value */ $emptyStringTypes = ['int', 'decimal', 'datetime', 'varchar', 'text', 'static']; - $attributeType = $this->getBackend()->getType(); return (is_array($value) && count($value) == 0) || $value === null - || ($value === false && $attributeType != 'int') - || ($value === '' && in_array($attributeType, $emptyStringTypes)); + || ($value === false && $this->getBackend()->getType() != 'int') + || ($value === '' && in_array($this->getBackend()->getType(), $emptyStringTypes)); } /** diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AttributeGroupAlreadyExistsException.php b/app/code/Magento/Eav/Model/Entity/Attribute/AttributeGroupAlreadyExistsException.php new file mode 100644 index 0000000000000000000000000000000000000000..ca80ca089a2ea4cc3b297b4dc933b53049ed31f1 --- /dev/null +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AttributeGroupAlreadyExistsException.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Eav\Model\Entity\Attribute; + +use Magento\Framework\Exception\AlreadyExistsException; + +/** + * Class AttributeGroupAlreadyExistsException + */ +class AttributeGroupAlreadyExistsException extends AlreadyExistsException +{ +} diff --git a/app/code/Magento/Eav/Model/ResourceModel/AttributeLoader.php b/app/code/Magento/Eav/Model/ResourceModel/AttributeLoader.php new file mode 100644 index 0000000000000000000000000000000000000000..439f550a2bf020fadd8ae18abfb2348f4b18cf6b --- /dev/null +++ b/app/code/Magento/Eav/Model/ResourceModel/AttributeLoader.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Eav\Model\ResourceModel; + +use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; +use Magento\Eav\Model\Entity\AttributeCache; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Сlass responsible for loading and caching of attributes related to the given attribute set. + * + * Can be used to improve performance of services that mostly read attribute data. + */ +class AttributeLoader +{ + /** Name of ATTRIBUTE_SET_ID field */ + const ATTRIBUTE_SET_ID = 'attribute_set_id'; + + /** + * @var AttributeRepository + */ + private $attributeRepository; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @var AttributeCache + */ + private $attributeCache; + + /** + * AttributeLoader constructor. + * @param AttributeRepository $attributeRepository + * @param MetadataPool $metadataPool + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param AttributeCache $attributeCache + */ + public function __construct( + AttributeRepository $attributeRepository, + MetadataPool $metadataPool, + SearchCriteriaBuilder $searchCriteriaBuilder, + AttributeCache $attributeCache + ) { + $this->attributeRepository = $attributeRepository; + $this->metadataPool = $metadataPool; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->attributeCache = $attributeCache; + } + + /** + * Get attributes list from attribute set + * + * @param string $entityType + * @param int $attributeSetId + * @return \Magento\Eav\Api\Data\AttributeInterface[]|\object[] + */ + public function getAttributes($entityType, $attributeSetId = null) + { + $suffix = self::ATTRIBUTE_SET_ID . '-' . ($attributeSetId ?: 'all'); + if ($attributes = $this->attributeCache->getAttributes($entityType, $suffix)) { + return $attributes; + } + + $metadata = $this->metadataPool->getMetadata($entityType); + + if ($attributeSetId === null) { + $criteria = $this->searchCriteriaBuilder->addFilter(self::ATTRIBUTE_SET_ID, null, 'neq')->create(); + } else { + $criteria = $this->searchCriteriaBuilder->addFilter(self::ATTRIBUTE_SET_ID, $attributeSetId)->create(); + } + + $searchResult = $this->attributeRepository->getList( + $metadata->getEavEntityType(), + $criteria + ); + $attributes = $searchResult->getItems(); + + $this->attributeCache->saveAttributes( + $entityType, + $attributes, + $suffix + ); + return $attributes; + } +} diff --git a/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php b/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php index 74373cb593c4086c06378050e1fa89b944d78be6..5e340595df9867b1cb02fd795c6fc9be7e34fc02 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php +++ b/app/code/Magento/Eav/Model/ResourceModel/AttributePersistor.php @@ -113,13 +113,14 @@ class AttributePersistor return; } $metadata = $this->metadataPool->getMetadata($entityType); + $linkField = $metadata->getLinkField(); foreach ($this->delete[$entityType] as $link => $data) { $attributeCodes = array_keys($data); foreach ($attributeCodes as $attributeCode) { /** @var AbstractAttribute $attribute */ $attribute = $this->attributeRepository->get($metadata->getEavEntityType(), $attributeCode); $conditions = [ - $metadata->getLinkField() . ' = ?' => $link, + $linkField . ' = ?' => $link, 'attribute_id = ?' => $attribute->getAttributeId() ]; foreach ($context as $scope) { @@ -147,6 +148,7 @@ class AttributePersistor return; } $metadata = $this->metadataPool->getMetadata($entityType); + $linkField = $metadata->getLinkField(); foreach ($this->insert[$entityType] as $link => $data) { foreach ($data as $attributeCode => $attributeValue) { /** @var AbstractAttribute $attribute */ @@ -155,7 +157,7 @@ class AttributePersistor $attributeCode ); $data = [ - $metadata->getLinkField() => $link, + $linkField => $link, 'attribute_id' => $attribute->getAttributeId(), 'value' => $this->prepareValue($entityType, $attributeValue, $attribute) ]; @@ -180,6 +182,7 @@ class AttributePersistor return; } $metadata = $this->metadataPool->getMetadata($entityType); + $linkField = $metadata->getLinkField(); foreach ($this->update[$entityType] as $link => $data) { foreach ($data as $attributeCode => $attributeValue) { /** @var AbstractAttribute $attribute */ @@ -188,7 +191,7 @@ class AttributePersistor $attributeCode ); $conditions = [ - $metadata->getLinkField() . ' = ?' => $link, + $linkField . ' = ?' => $link, 'attribute_id = ?' => $attribute->getAttributeId(), ]; foreach ($context as $scope) { diff --git a/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php b/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php index 1a8aaaf027f561966101e360020fb3a2441e27d6..df411b6a21698033c60839c0d855c3b0ed4e96e5 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php +++ b/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php @@ -6,9 +6,10 @@ namespace Magento\Eav\Model\ResourceModel; use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; -use Magento\Framework\EntityManager\Operation\AttributeInterface; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\EntityManager\Operation\AttributeInterface; use Magento\Framework\Model\Entity\ScopeResolver; /** @@ -42,40 +43,43 @@ class CreateHandler implements AttributeInterface */ private $scopeResolver; + /** + * @var AttributeLoader + */ + private $attributeLoader; + /** * @param AttributeRepository $attributeRepository * @param MetadataPool $metadataPool * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param AttributePersistor $attributePersistor * @param ScopeResolver $scopeResolver + * @param AttributeLoader $attributeLoader */ public function __construct( AttributeRepository $attributeRepository, MetadataPool $metadataPool, SearchCriteriaBuilder $searchCriteriaBuilder, AttributePersistor $attributePersistor, - ScopeResolver $scopeResolver + ScopeResolver $scopeResolver, + AttributeLoader $attributeLoader = null ) { $this->attributeRepository = $attributeRepository; $this->metadataPool = $metadataPool; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->attributePersistor = $attributePersistor; $this->scopeResolver = $scopeResolver; + $this->attributeLoader = $attributeLoader ?: ObjectManager::getInstance()->get(AttributeLoader::class); } /** * @param string $entityType + * @param int $attributeSetId * @return \Magento\Eav\Api\Data\AttributeInterface[] - * @throws \Exception */ - protected function getAttributes($entityType) + protected function getAttributes($entityType, $attributeSetId = null) { - $metadata = $this->metadataPool->getMetadata($entityType); - $searchResult = $this->attributeRepository->getList( - $metadata->getEavEntityType(), - $this->searchCriteriaBuilder->addFilter('attribute_set_id', null, 'neq')->create() - ); - return $searchResult->getItems(); + return $this->attributeLoader->getAttributes($entityType, $attributeSetId); } /** @@ -92,23 +96,28 @@ class CreateHandler implements AttributeInterface $metadata = $this->metadataPool->getMetadata($entityType); if ($metadata->getEavEntityType()) { $processed = []; + $entityLinkField = $metadata->getLinkField(); + $attributeSetId = isset($entityData[AttributeLoader::ATTRIBUTE_SET_ID]) + ? $entityData[AttributeLoader::ATTRIBUTE_SET_ID] + : null; // @todo verify is it normal to not have attributer_set_id /** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute */ - foreach ($this->getAttributes($entityType) as $attribute) { + foreach ($this->getAttributes($entityType, $attributeSetId) as $attribute) { if ($attribute->isStatic()) { continue; } - if (isset($entityData[$attribute->getAttributeCode()]) - && !is_array($entityData[$attribute->getAttributeCode()]) - && !$attribute->isValueEmpty($entityData[$attribute->getAttributeCode()]) + + $attributeCode = $attribute->getAttributeCode(); + if (isset($entityData[$attributeCode]) + && !is_array($entityData[$attributeCode]) + && !$attribute->isValueEmpty($entityData[$attributeCode]) ) { - $entityLinkField = $metadata->getLinkField(); $this->attributePersistor->registerInsert( $entityType, $entityData[$entityLinkField], - $attribute->getAttributeCode(), - $entityData[$attribute->getAttributeCode()] + $attributeCode, + $entityData[$attributeCode] ); - $processed[$attribute->getAttributeCode()] = $entityData[$attribute->getAttributeCode()]; + $processed[$attributeCode] = $entityData[$attributeCode]; } } $context = $this->scopeResolver->getEntityContext($entityType, $entityData); diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Group.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Group.php index 796f4d19f2eb8d90c4625542a33257e3a065a5af..9515bbc66437859d984f126b4d49f483c72f8194 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Group.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Group.php @@ -5,6 +5,10 @@ */ namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; +use Magento\Eav\Model\Entity\Attribute\AttributeGroupAlreadyExistsException; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\Model\AbstractModel; + /** * Eav Resource Entity Attribute Group * @@ -50,10 +54,10 @@ class Group extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb /** * Perform actions before object save * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ - protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) + protected function _beforeSave(AbstractModel $object) { if (!$object->getSortOrder()) { $object->setSortOrder($this->_getMaxSortOrder($object) + 1); @@ -64,10 +68,10 @@ class Group extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb /** * Perform actions after object save * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ - protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) + protected function _afterSave(AbstractModel $object) { if ($object->getAttributes()) { foreach ($object->getAttributes() as $attribute) { @@ -82,7 +86,7 @@ class Group extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb /** * Retrieve max sort order * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return int */ protected function _getMaxSortOrder($object) @@ -130,4 +134,38 @@ class Group extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $this; } + + /** + * {@inheritdoc} + */ + protected function saveNewObject(AbstractModel $object) + { + try { + return parent::saveNewObject($object); + } catch (DuplicateException $e) { + throw new AttributeGroupAlreadyExistsException( + __( + 'Attribute group with same code already exist. Please rename "%1" group', + $object->getAttributeGroupName() + ) + ); + } + } + + /** + * {@inheritdoc} + */ + protected function updateObject(AbstractModel $object) + { + try { + return parent::updateObject($object); + } catch (DuplicateException $e) { + throw new AttributeGroupAlreadyExistsException( + __( + 'Attribute group with same code already exist. Please rename "%1" group', + $object->getAttributeGroupName() + ) + ); + } + } } diff --git a/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php b/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php index 0f892a272fa1a209ca7b2e632abc98db5afb5d3d..c775a24a03c465dad45640a7f634823cb8b43ef8 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php +++ b/app/code/Magento/Eav/Model/ResourceModel/UpdateHandler.php @@ -54,6 +54,11 @@ class UpdateHandler implements AttributeInterface */ private $readHandler; + /** + * @var AttributeLoader + */ + private $attributeLoader; + /** * UpdateHandler constructor. * @param AttributeRepository $attributeRepository @@ -62,6 +67,7 @@ class UpdateHandler implements AttributeInterface * @param AttributePersistor $attributePersistor * @param ReadSnapshot $readSnapshot * @param ScopeResolver $scopeResolver + * @param AttributeLoader $attributeLoader */ public function __construct( AttributeRepository $attributeRepository, @@ -69,7 +75,8 @@ class UpdateHandler implements AttributeInterface SearchCriteriaBuilder $searchCriteriaBuilder, AttributePersistor $attributePersistor, ReadSnapshot $readSnapshot, - ScopeResolver $scopeResolver + ScopeResolver $scopeResolver, + AttributeLoader $attributeLoader = null ) { $this->attributeRepository = $attributeRepository; $this->metadataPool = $metadataPool; @@ -77,22 +84,17 @@ class UpdateHandler implements AttributeInterface $this->attributePersistor = $attributePersistor; $this->readSnapshot = $readSnapshot; $this->scopeResolver = $scopeResolver; + $this->attributeLoader = $attributeLoader ?: ObjectManager::getInstance()->get(AttributeLoader::class); } /** * @param string $entityType + * @param int $attributeSetId * @return \Magento\Eav\Api\Data\AttributeInterface[] - * @throws \Exception */ - protected function getAttributes($entityType) + protected function getAttributes($entityType, $attributeSetId = null) { - $metadata = $this->metadataPool->getMetadata($entityType); - - $searchResult = $this->attributeRepository->getList( - $metadata->getEavEntityType(), - $this->searchCriteriaBuilder->addFilter('attribute_set_id', null, 'neq')->create() - ); - return $searchResult->getItems(); + return $this->attributeLoader->getAttributes($entityType, $attributeSetId); } /** @@ -118,8 +120,11 @@ class UpdateHandler implements AttributeInterface $entityDataForSnapshot[$scope->getIdentifier()] = $entityData[$scope->getIdentifier()]; } } + $attributeSetId = isset($entityData[AttributeLoader::ATTRIBUTE_SET_ID]) + ? $entityData[AttributeLoader::ATTRIBUTE_SET_ID] + : null; // @todo verify is it normal to not have attributer_set_id $snapshot = $this->readSnapshot->execute($entityType, $entityDataForSnapshot); - foreach ($this->getAttributes($entityType) as $attribute) { + foreach ($this->getAttributes($entityType, $attributeSetId) as $attribute) { if ($attribute->isStatic()) { continue; } diff --git a/app/code/Magento/Eav/Setup/UpgradeSchema.php b/app/code/Magento/Eav/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..2b52bba7c2efbd379851ddf16a0cafbc077dcaf2 --- /dev/null +++ b/app/code/Magento/Eav/Setup/UpgradeSchema.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Eav\Setup; + +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * Upgrade the Eav module DB scheme + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.1.0', '<')) { + $this->addUniqueKeyToEavAttributeGroupTable($setup); + } + $setup->endSetup(); + } + + /** + * @param SchemaSetupInterface $setup + * @return void + */ + private function addUniqueKeyToEavAttributeGroupTable(SchemaSetupInterface $setup) + { + $setup->getConnection()->addIndex( + $setup->getTable('eav_attribute_group'), + $setup->getIdxName( + 'catalog_category_product', + ['attribute_set_id', 'attribute_group_code'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['attribute_set_id', 'attribute_group_code'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ); + } +} diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php index f39eeb892757645df9fc3157894249e18d8c4803..7628207e25a52c2ea7cd595bd47f65fb0d18ef81 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php @@ -5,13 +5,17 @@ */ namespace Magento\Eav\Test\Unit\Model\Entity; +use Magento\Eav\Model\Entity\AbstractEntity; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\Model\AbstractModel; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class AbstractEntityTest extends \PHPUnit_Framework_TestCase { /** * Entity model to be tested - * @var \Magento\Eav\Model\Entity\AbstractEntity|\PHPUnit_Framework_MockObject_MockObject + * @var AbstractEntity|\PHPUnit_Framework_MockObject_MockObject */ protected $_model; @@ -23,11 +27,11 @@ class AbstractEntityTest extends \PHPUnit_Framework_TestCase $objectManager = new ObjectManager($this); $this->eavConfig = $this->getMock(\Magento\Eav\Model\Config::class, [], [], '', false); $arguments = $objectManager->getConstructArguments( - \Magento\Eav\Model\Entity\AbstractEntity::class, + AbstractEntity::class, ['eavConfig' => $this->eavConfig] ); $this->_model = $this->getMockForAbstractClass( - \Magento\Eav\Model\Entity\AbstractEntity::class, + AbstractEntity::class, $arguments ); } @@ -113,7 +117,7 @@ class AbstractEntityTest extends \PHPUnit_Framework_TestCase /** * Get adapter mock * - * @return \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\DB\Adapter\AdapterInterface + * @return \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\DB\Adapter\Pdo\Mysql */ protected function _getConnectionMock() { @@ -300,7 +304,7 @@ class AbstractEntityTest extends \PHPUnit_Framework_TestCase $objectManager = new ObjectManager($this); $this->eavConfig = $this->getMock(\Magento\Eav\Model\Config::class, [], [], '', false); $arguments = $objectManager->getConstructArguments( - \Magento\Eav\Model\Entity\AbstractEntity::class, + AbstractEntity::class, [ 'eavConfig' => $eavConfig, 'data' => [ @@ -310,8 +314,8 @@ class AbstractEntityTest extends \PHPUnit_Framework_TestCase ] ] ); - /** @var $model \Magento\Framework\Model\AbstractModel|\PHPUnit_Framework_MockObject_MockObject */ - $model = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) + /** @var $model AbstractEntity|\PHPUnit_Framework_MockObject_MockObject */ + $model = $this->getMockBuilder(AbstractEntity::class) ->setConstructorArgs($arguments) ->setMethods(['_getValue', 'beginTransaction', 'commit', 'rollback', 'getConnection']) ->getMock(); @@ -353,4 +357,30 @@ class AbstractEntityTest extends \PHPUnit_Framework_TestCase ] ]; } + + /** + * @expectedException \Magento\Framework\Exception\AlreadyExistsException + */ + public function testDuplicateExceptionProcessingOnSave() + { + $connection = $this->getMock(AdapterInterface::class); + $connection->expects($this->once())->method('rollback'); + + /** @var AbstractEntity|\PHPUnit_Framework_MockObject_MockObject $model */ + $model = $this->getMockBuilder(AbstractEntity::class) + ->disableOriginalConstructor() + ->setMethods(['getConnection']) + ->getMockForAbstractClass(); + $model->expects($this->any())->method('getConnection')->willReturn($connection); + + /** @var AbstractModel|\PHPUnit_Framework_MockObject_MockObject $object */ + $object = $this->getMockBuilder(AbstractModel::class) + ->disableOriginalConstructor() + ->getMock(); + $object->expects($this->once())->method('hasDataChanges')->willReturn(true); + $object->expects($this->once())->method('beforeSave')->willThrowException(new DuplicateException()); + $object->expects($this->once())->method('setHasDataChanges')->with(true); + + $model->save($object); + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php index d6b88e0ac56915c3b116ac740b18cfd31ae9f876..a10bafacda42dc2b72fcc285307a0250cae3074b 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/AbstractAttributeTest.php @@ -207,7 +207,7 @@ class AbstractAttributeTest extends \PHPUnit_Framework_TestCase ] ); $backendModelMock->expects($this->any())->method('getType')->willReturn($attributeType); - $model->expects($this->once())->method('getBackend')->willReturn($backendModelMock); + $model->expects($this->any())->method('getBackend')->willReturn($backendModelMock); $this->assertEquals($isEmpty, $model->isValueEmpty($value)); } diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml index e273eab770c170c5ab6ff6b27da18ecdf41fc7c6..0fcba9907cc4d46b76cf0b7e86c2e0e1bf61da5a 100644 --- a/app/code/Magento/Eav/etc/di.xml +++ b/app/code/Magento/Eav/etc/di.xml @@ -152,9 +152,12 @@ <argument name="collectionProcessor" xsi:type="object">Magento\Eav\Model\Api\SearchCriteria\AttributeGroupCollectionProcessor</argument> </arguments> </type> - <type name="Magento\Framework\EntityManager\CustomAttributesMapper"> + <type name="Magento\Framework\EntityManager\CompositeMapper"> <arguments> - <argument name="mapper" xsi:type="object">Magento\Eav\Model\CustomAttributesMapper</argument> + <argument name="mappers" xsi:type="array"> + <item name="mapper" xsi:type="object">Magento\Framework\EntityManager\Mapper</item> + <item name="customAttributesMapper" xsi:type="object">Magento\Eav\Model\CustomAttributesMapper</item> + </argument> </arguments> </type> </config> diff --git a/app/code/Magento/Eav/etc/module.xml b/app/code/Magento/Eav/etc/module.xml index c1c313d91501989578f03b404ae91acc2d0ccbb1..d03606d1eeb900a1dfec428c172cba0b47a01fc0 100644 --- a/app/code/Magento/Eav/etc/module.xml +++ b/app/code/Magento/Eav/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Eav" setup_version="2.0.0"> + <module name="Magento_Eav" setup_version="2.1.0"> <sequence> <module name="Magento_Store"/> </sequence> diff --git a/app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php b/app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php new file mode 100644 index 0000000000000000000000000000000000000000..02c1fee1b0745efbdee91e025f7b7c4d9b6f03b4 --- /dev/null +++ b/app/code/Magento/Email/Model/Mail/TransportInterfacePlugin.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Email\Model\Mail; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\MailException; +use Magento\Framework\Mail\TransportInterface; +use Magento\Store\Model\ScopeInterface; + +class TransportInterfacePlugin +{ + /** + * Config path to mail sending setting that shows if email communications are disabled + */ + const XML_PATH_SYSTEM_SMTP_DISABLE = 'system/smtp/disable'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * Omit email sending if disabled + * + * @param TransportInterface $subject + * @param \Closure $proceed + * @return void + * @throws MailException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundSendMessage( + TransportInterface $subject, + \Closure $proceed + ) { + if (!$this->scopeConfig->isSetFlag(self::XML_PATH_SYSTEM_SMTP_DISABLE, ScopeInterface::SCOPE_STORE)) { + $proceed(); + } + } +} diff --git a/app/code/Magento/Email/Model/Template.php b/app/code/Magento/Email/Model/Template.php index c281a08ab97b6bd153b77a0803b7d2c21792cc50..c42c4088793d7c429bf246bd3090731751e4ddff 100644 --- a/app/code/Magento/Email/Model/Template.php +++ b/app/code/Magento/Email/Model/Template.php @@ -5,26 +5,11 @@ */ namespace Magento\Email\Model; -use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; /** * Template model * - * Example: - * - * // Loading of template - * \Magento\Email\Model\TemplateFactory $templateFactory - * $templateFactory->create()->load($this->_scopeConfig->getValue( - * 'path_to_email_template_id_config', - * \Magento\Store\Model\ScopeInterface::SCOPE_STORE - * )); - * $variables = array( - * 'someObject' => $this->_coreResourceEmailTemplate - * 'someString' => 'Some string value' - * ); - * $emailTemplate->send('some@domain.com', 'Name Of User', $variables); - * * @method \Magento\Email\Model\ResourceModel\Template _getResource() * @method \Magento\Email\Model\ResourceModel\Template getResource() * @method string getTemplateCode() @@ -63,6 +48,8 @@ class Template extends AbstractTemplate implements \Magento\Framework\Mail\Templ /** * Config path to mail sending setting that shows if email communications are disabled + * @deprecated + * @see \Magento\Email\Model\Mail\TransportInterfacePlugin::XML_PATH_SYSTEM_SMTP_DISABLE */ const XML_PATH_SYSTEM_SMTP_DISABLE = 'system/smtp/disable'; @@ -196,8 +183,7 @@ class Template extends AbstractTemplate implements \Magento\Framework\Mail\Templ */ public function isValidForSend() { - return !$this->scopeConfig->isSetFlag(Template::XML_PATH_SYSTEM_SMTP_DISABLE, ScopeInterface::SCOPE_STORE) - && $this->getSenderName() && $this->getSenderEmail() && $this->getTemplateSubject(); + return $this->getSenderName() && $this->getSenderEmail() && $this->getTemplateSubject(); } /** @@ -344,7 +330,8 @@ class Template extends AbstractTemplate implements \Magento\Framework\Mail\Templ if ($this->_getResource()->checkCodeUsage($this)) { throw new \Magento\Framework\Exception\MailException(__('Duplicate Of Template Name')); } - return parent::beforeSave(); + parent::beforeSave(); + return $this; } /** diff --git a/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php index f2195a64220e25effc00550af98c93d21e5e0145..3ee95954c0a3af7d122d62631e77f94d54ad5dda 100644 --- a/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/TemplateTest.php @@ -5,7 +5,6 @@ */ namespace Magento\Email\Test\Unit\Model; -use Magento\Email\Model\Template\Filter; use Magento\Framework\App\Area; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\TemplateTypesInterface; @@ -426,18 +425,13 @@ class TemplateTest extends \PHPUnit_Framework_TestCase } /** - * @param $isSMTPDisabled bool * @param $senderName string * @param $senderEmail string * @param $templateSubject string * @dataProvider isValidForSendDataProvider */ - public function testIsValidForSend($isSMTPDisabled, $senderName, $senderEmail, $templateSubject, $expectedValue) + public function testIsValidForSend($senderName, $senderEmail, $templateSubject, $expectedValue) { - $this->scopeConfig->expects($this->once()) - ->method('isSetFlag') - ->with('system/smtp/disable', ScopeInterface::SCOPE_STORE) - ->will($this->returnValue($isSMTPDisabled)); $model = $this->getModelMock(['getSenderName', 'getSenderEmail', 'getTemplateSubject']); $model->expects($this->any()) ->method('getSenderName') @@ -455,35 +449,24 @@ class TemplateTest extends \PHPUnit_Framework_TestCase { return [ 'should be valid' => [ - 'isSMTPDisabled' => false, 'senderName' => 'sender name', 'senderEmail' => 'email@example.com', 'templateSubject' => 'template subject', 'expectedValue' => true ], - 'no smtp so not valid' => [ - 'isSMTPDisabled' => true, - 'senderName' => 'sender name', - 'senderEmail' => 'email@example.com', - 'templateSubject' => 'template subject', - 'expectedValue' => false - ], 'no sender name so not valid' => [ - 'isSMTPDisabled' => false, 'senderName' => '', 'senderEmail' => 'email@example.com', 'templateSubject' => 'template subject', 'expectedValue' => false ], 'no sender email so not valid' => [ - 'isSMTPDisabled' => false, 'senderName' => 'sender name', 'senderEmail' => '', 'templateSubject' => 'template subject', 'expectedValue' => false ], 'no subject so not valid' => [ - 'isSMTPDisabled' => false, 'senderName' => 'sender name', 'senderEmail' => 'email@example.com', 'templateSubject' => '', diff --git a/app/code/Magento/Email/etc/di.xml b/app/code/Magento/Email/etc/di.xml index d1477d470abd1d35416e4ba40d246ecd90148c26..380e5e6248fb3f3b6a42108f91a2a4f49b08e263 100644 --- a/app/code/Magento/Email/etc/di.xml +++ b/app/code/Magento/Email/etc/di.xml @@ -59,5 +59,6 @@ </type> <type name="Magento\Framework\Mail\TransportInterface"> <plugin name="WindowsSmtpConfig" type="Magento\Email\Model\Plugin\WindowsSmtpConfig" /> + <plugin name="disableSending" type="Magento\Email\Model\Mail\TransportInterfacePlugin" /> </type> </config> diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php index a56642d1dda33952bdb8d238dc074428a170b54b..fec1684f39f36ca7e259863bba817119cc896054 100644 --- a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php +++ b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php @@ -86,6 +86,16 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic 'values' => $this->_formatFactory->create()->toOptionArray() ] ); + $fieldset->addField( + \Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE, + 'checkbox', + [ + 'name' => \Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE, + 'label' => __('Fields Enclosure'), + 'title' => __('Fields Enclosure'), + 'value' => 1, + ] + ); $form->setUseContainer(true); $this->setForm($form); diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php index 1511f58d4b039c5376ef293b8c011d4e2b0e7396..435a663923306da40741c2c9a43a6a234944a98a 100644 --- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php +++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php @@ -174,6 +174,16 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic 'value' => Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, ] ); + $fieldsets[$behaviorCode]->addField( + $behaviorCode . \Magento\ImportExport\Model\Import::FIELDS_ENCLOSURE, + 'checkbox', + [ + 'name' => \Magento\ImportExport\Model\Import::FIELDS_ENCLOSURE, + 'label' => __('Fields enclosure'), + 'title' => __('Fields enclosure'), + 'value' => 1, + ] + ); } // fieldset for file uploading diff --git a/app/code/Magento/ImportExport/Model/Export.php b/app/code/Magento/ImportExport/Model/Export.php index 2785327326e89359c6aabbbedb4e38cb136c1dfa..3b833cd4ab8834fc07a86ef9a52b51d66140ea3a 100644 --- a/app/code/Magento/ImportExport/Model/Export.php +++ b/app/code/Magento/ImportExport/Model/Export.php @@ -20,6 +20,11 @@ class Export extends \Magento\ImportExport\Model\AbstractModel const FILTER_ELEMENT_SKIP = 'skip_attr'; + /** + * Allow multiple values wrapping in double quotes for additional attributes. + */ + const FIELDS_ENCLOSURE = 'fields_enclosure'; + /** * Filter fields types. */ diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php index b7ba3196de4566950d45bc0a335310d77b6c6202..89b5f88f0e9c2d4e071cdc0ec46d97bf731e64e8 100644 --- a/app/code/Magento/ImportExport/Model/Import.php +++ b/app/code/Magento/ImportExport/Model/Import.php @@ -78,6 +78,11 @@ class Import extends \Magento\ImportExport\Model\AbstractModel */ const FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR = '_import_multiple_value_separator'; + /** + * Allow multiple values wrapping in double quotes for additional attributes. + */ + const FIELDS_ENCLOSURE = 'fields_enclosure'; + /**#@-*/ /** @@ -605,7 +610,10 @@ class Import extends \Magento\ImportExport\Model\AbstractModel foreach (array_keys($relatedIndexers) as $indexerId) { try { $indexer = $this->indexerRegistry->get($indexerId); - $indexer->invalidate(); + + if (!$indexer->isScheduled()) { + $indexer->invalidate(); + } } catch (\InvalidArgumentException $e) { } } diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php index f39acf7add05dc30cc91e56e2fbb7e1e7d6a6fd2..4c175b64924b951465ba9d512900e56570180ad4 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php @@ -5,6 +5,9 @@ */ namespace Magento\ImportExport\Test\Unit\Model; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\ImportExport\Model\Import; + /** * Class ImportTest * @package Magento\ImportExport\Test\Unit\Model @@ -126,7 +129,7 @@ class ImportTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm ->getMock(); $this->_importConfig = $this->getMockBuilder(\Magento\ImportExport\Model\Import\Config::class) ->disableOriginalConstructor() - ->setMethods(['getEntityTypeCode', 'getBehavior', 'getEntities']) + ->setMethods(['getEntityTypeCode', 'getBehavior', 'getEntities', 'getRelatedIndexers']) ->getMockForAbstractClass(); $this->_entityFactory = $this->getMockBuilder(\Magento\ImportExport\Model\Import\Entity\Factory::class) ->disableOriginalConstructor() @@ -419,12 +422,90 @@ class ImportTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm $this->markTestIncomplete('This test has not been implemented yet.'); } - /** - * @todo to implement it. - */ public function testInvalidateIndex() { - $this->markTestIncomplete('This test has not been implemented yet.'); + $indexers = [ + 'indexer_1' => 'indexer_1', + 'indexer_2' => 'indexer_2' + ]; + $indexer1 = $this->getMockBuilder(IndexerInterface::class) + ->getMockForAbstractClass(); + $indexer2 = clone $indexer1; + $logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $indexer1->expects($this->once()) + ->method('isScheduled') + ->willReturn(true); + $indexer1->expects($this->never()) + ->method('invalidate'); + $indexer2->expects($this->once()) + ->method('isScheduled') + ->willReturn(false); + $indexer2->expects($this->once()) + ->method('invalidate'); + + $this->_importConfig->expects($this->atLeastOnce()) + ->method('getRelatedIndexers') + ->willReturn($indexers); + $this->indexerRegistry->expects($this->any()) + ->method('get') + ->willReturnMap([ + ['indexer_1', $indexer1], + ['indexer_2', $indexer2], + ]); + + $import = new Import( + $logger, + $this->_filesystem, + $this->_importExportData, + $this->_coreConfig, + $this->_importConfig, + $this->_entityFactory, + $this->_importData, + $this->_csvFactory, + $this->_httpFactory, + $this->_uploaderFactory, + $this->_behaviorFactory, + $this->indexerRegistry, + $this->historyModel, + $this->dateTime + ); + + $import->setEntity('test'); + $import->invalidateIndex(); + } + + public function testInvalidateIndexWithoutIndexers() + { + $this->_importConfig->expects($this->once()) + ->method('getRelatedIndexers') + ->willReturn([]); + + $logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $import = new Import( + $logger, + $this->_filesystem, + $this->_importExportData, + $this->_coreConfig, + $this->_importConfig, + $this->_entityFactory, + $this->_importData, + $this->_csvFactory, + $this->_httpFactory, + $this->_uploaderFactory, + $this->_behaviorFactory, + $this->indexerRegistry, + $this->historyModel, + $this->dateTime + ); + + $import->setEntity('test'); + $this->assertSame($import, $import->invalidateIndex()); } /** diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml index b8acac6a1ab64dbec581785293c16ad18c5218f2..ce4f61cef4febc90a3e802c7dacc9b2e09fb28c3 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml @@ -80,6 +80,9 @@ require([ var oldAction = form.action; var url = oldAction + ((oldAction.slice(-1) != '/') ? '/' : '') + 'entity/' + $F('entity') + '/file_format/' + $F('file_format'); + if ($F('fields_enclosure')) { + url += '/fields_enclosure/' + $F('fields_enclosure'); + } form.action = url; form.submit(); form.action = oldAction; diff --git a/app/code/Magento/Indexer/Setup/RecurringData.php b/app/code/Magento/Indexer/Setup/RecurringData.php new file mode 100644 index 0000000000000000000000000000000000000000..bd6f8f0241dc81cf5a632a355f5193c82e180023 --- /dev/null +++ b/app/code/Magento/Indexer/Setup/RecurringData.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Indexer\Setup; + +use Magento\Framework\Setup\InstallDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Indexer\Model\IndexerFactory; +use Magento\Framework\Indexer\ConfigInterface; + +/** + * Recurring data upgrade for indexer module + */ +class RecurringData implements InstallDataInterface +{ + /** + * @var IndexerFactory + */ + private $indexerFactory; + + /** + * @var ConfigInterface + */ + private $configInterface; + + /** + * RecurringData constructor. + * + * @param IndexerFactory $indexerFactory + * @param ConfigInterface $configInterface + */ + public function __construct( + IndexerFactory $indexerFactory, + ConfigInterface $configInterface + ) { + $this->indexerFactory = $indexerFactory; + $this->configInterface = $configInterface; + } + + /** + * {@inheritdoc} + */ + public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + foreach (array_keys($this->configInterface->getIndexers()) as $indexerId) { + $indexer = $this->indexerFactory->create()->load($indexerId); + if ($indexer->isScheduled()) { + $indexer->getView()->unsubscribe()->subscribe(); + } + } + } +} diff --git a/app/code/Magento/Newsletter/Model/Template.php b/app/code/Magento/Newsletter/Model/Template.php index 7b454921b0ddc490a139aa35402d987b6cbdeda9..09ea825c612af13a08636a6d4d302ec65cad9a4f 100644 --- a/app/code/Magento/Newsletter/Model/Template.php +++ b/app/code/Magento/Newsletter/Model/Template.php @@ -174,7 +174,8 @@ class Template extends \Magento\Email\Model\AbstractTemplate public function beforeSave() { $this->validate(); - return parent::beforeSave(); + parent::beforeSave(); + return $this; } /** @@ -238,9 +239,6 @@ class Template extends \Magento\Email\Model\AbstractTemplate */ public function isValidForSend() { - return !$this->scopeConfig->isSetFlag( - \Magento\Email\Model\Template::XML_PATH_SYSTEM_SMTP_DISABLE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) && $this->getTemplateSenderName() && $this->getTemplateSenderEmail() && $this->getTemplateSubject(); + return $this->getTemplateSenderName() && $this->getTemplateSenderEmail() && $this->getTemplateSubject(); } } diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php index e51d5e0fdb3a75f6abcf79cd6289b398ec0e9391..c8599230652425348ce1b8eea8d0679defa9df16 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/TemplateTest.php @@ -379,4 +379,55 @@ class TemplateTest extends \PHPUnit_Framework_TestCase ], ]; } + + /** + * @param $senderName string + * @param $senderEmail string + * @param $templateSubject string + * @dataProvider isValidForSendDataProvider + */ + public function testIsValidForSend($senderName, $senderEmail, $templateSubject, $expectedValue) + { + $model = $this->getModelMock(['getTemplateSenderName', 'getTemplateSenderEmail', 'getTemplateSubject']); + $model->expects($this->any()) + ->method('getTemplateSenderName') + ->will($this->returnValue($senderName)); + $model->expects($this->any()) + ->method('getTemplateSenderEmail') + ->will($this->returnValue($senderEmail)); + $model->expects($this->any()) + ->method('getTemplateSubject') + ->will($this->returnValue($templateSubject)); + $this->assertEquals($expectedValue, $model->isValidForSend()); + } + + public function isValidForSendDataProvider() + { + return [ + 'should be valid' => [ + 'senderName' => 'sender name', + 'senderEmail' => 'email@example.com', + 'templateSubject' => 'template subject', + 'expectedValue' => true + ], + 'no sender name so not valid' => [ + 'senderName' => '', + 'senderEmail' => 'email@example.com', + 'templateSubject' => 'template subject', + 'expectedValue' => false + ], + 'no sender email so not valid' => [ + 'senderName' => 'sender name', + 'senderEmail' => '', + 'templateSubject' => 'template subject', + 'expectedValue' => false + ], + 'no subject so not valid' => [ + 'senderName' => 'sender name', + 'senderEmail' => 'email@example.com', + 'templateSubject' => '', + 'expectedValue' => false + ], + ]; + } } diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index c4d9657a322dab4b48a1668f908021de223f050b..d176b5be6a4551d9d7162ebf5d9436f99aadcd37 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -151,25 +151,28 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen $request->setPackageQty($oldQty); if (!empty($rate) && $rate['price'] >= 0) { - /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */ - $method = $this->_resultMethodFactory->create(); - - $method->setCarrier('tablerate'); - $method->setCarrierTitle($this->getConfigData('title')); - - $method->setMethod('bestway'); - $method->setMethodTitle($this->getConfigData('name')); - if ($request->getFreeShipping() === true || $request->getPackageQty() == $freeQty) { $shippingPrice = 0; } else { $shippingPrice = $this->getFinalPriceWithHandlingFee($rate['price']); } - - $method->setPrice($shippingPrice); - $method->setCost($rate['cost']); - + $method = $this->createShippingMethod($shippingPrice, $rate['cost']); $result->append($method); + } elseif (empty($rate) && $request->getFreeShipping() === true || $request->getPackageQty() == $freeQty) { + + /** + * Promotion rule was applied for the whole cart. + * In this case all other shipping methods could be omitted + * Table rate shipping method with 0$ price must be shown if grand total is more than minimal value. + * Free package weight has been already taken into account. + */ + $request->setPackageValue($freePackageValue); + $request->setPackageQty($freeQty); + $rate = $this->getRate($request); + if (!empty($rate) && $rate['price'] >= 0) { + $method = $this->createShippingMethod(0, 0); + $result->append($method); + } } else { /** @var \Magento\Quote\Model\Quote\Address\RateResult\Error $error */ $error = $this->_rateErrorFactory->create( @@ -241,4 +244,27 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen { return ['bestway' => $this->getConfigData('name')]; } + + /** + * Get the method object based on the shipping price and cost + * + * @param float $shippingPrice + * @param float $cost + * @return \Magento\Quote\Model\Quote\Address\RateResult\Method + */ + private function createShippingMethod($shippingPrice, $cost) + { + /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */ + $method = $this->_resultMethodFactory->create(); + + $method->setCarrier('tablerate'); + $method->setCarrierTitle($this->getConfigData('title')); + + $method->setMethod('bestway'); + $method->setMethodTitle($this->getConfigData('name')); + + $method->setPrice($shippingPrice); + $method->setCost($cost); + return $method; + } } diff --git a/app/code/Magento/Payment/view/adminhtml/web/transparent.js b/app/code/Magento/Payment/view/adminhtml/web/transparent.js index 583945d39612e67ad99318dafca192a9355bd453..b94e9bd4b2673465e807fda1c54edf6af36f5980 100644 --- a/app/code/Magento/Payment/view/adminhtml/web/transparent.js +++ b/app/code/Magento/Payment/view/adminhtml/web/transparent.js @@ -6,12 +6,14 @@ define([ 'jquery', 'mage/template', - 'Magento_Ui/js/modal/alert' + 'Magento_Ui/js/modal/alert', + 'Magento_Payment/js/model/credit-card-validation/validator' ], function ($, mageTemplate, alert) { 'use strict'; $.widget('mage.transparent', { options: { + editFormSelector: '#edit_form', hiddenFormTmpl: '<form target="<%= data.target %>" action="<%= data.action %>"' + 'method="POST" hidden' + @@ -33,37 +35,51 @@ define([ * @private */ _create: function () { + this.hiddenFormTmpl = mageTemplate(this.options.hiddenFormTmpl); - /** - * @param {Object} event - * @param {String} method - */ - var prepare = function (event, method) { - if (method === this.options.gateway) { - $('#edit_form') - .off('submitOrder') - .on('submitOrder.' + this.options.gateway, this._orderSave.bind(this)); - } else { - $('#edit_form') - .off('submitOrder.' + this.options.gateway); - } - }, - $editForm = $('#edit_form'); + $(this.options.editFormSelector).on('changePaymentMethod', this._setPlaceOrderHandler.bind(this)); + $(this.options.editFormSelector).trigger('changePaymentMethod', [ + $(this.options.editFormSelector).find(':radio[name="payment[method]"]:checked').val() + ]); + }, - this.hiddenFormTmpl = mageTemplate(this.options.hiddenFormTmpl); - $editForm.on('changePaymentMethod', prepare.bind(this)); - - $editForm.trigger( - 'changePaymentMethod', - [ - $editForm.find(':radio[name="payment[method]"]:checked').val() - ] - ); + /** + * Handler for form submit. + * + * @param {Object} event + * @param {String} method + */ + _setPlaceOrderHandler: function (event, method) { + if (method === this.options.gateway) { + $(this.options.editFormSelector) + .off('submitOrder') + .on('submitOrder.' + this.options.gateway, this._placeOrderHandler.bind(this)); + } else { + $(this.options.editFormSelector) + .off('submitOrder.' + this.options.gateway); + } + }, + + /** + * Handler for form submit to call gateway for credit card validation. + * + * @return {Boolean} + * @private + */ + _placeOrderHandler: function () { + if ($(this.options.editFormSelector).valid()) { + this._orderSave(); + } else { + $('body').trigger('processStop'); + } + + return false; }, /** - * handler for Place Order button to call gateway for credit card validation - * Save order and generate post data for gateway call + * Handler for Place Order button to call gateway for credit card validation. + * Save order and generate post data for gateway call. + * * @private */ _orderSave: function () { @@ -99,7 +115,8 @@ define([ }, /** - * Post data to gateway for credit card validation + * Post data to gateway for credit card validation. + * * @param {Object} response * @private */ @@ -136,7 +153,7 @@ define([ }, /** - * Add credit card fields to post data for gateway + * Add credit card fields to post data for gateway. * * @param {Object} response * @private diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-data.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-data.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-data.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-data.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/cvv-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/cvv-validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js diff --git a/app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/validator.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/validator.js similarity index 100% rename from app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/validator.js rename to app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/validator.js diff --git a/app/code/Magento/Paypal/view/adminhtml/templates/transparent/form.phtml b/app/code/Magento/Paypal/view/adminhtml/templates/transparent/form.phtml index b1a50bcb1bf73db9232cf3457e26215bebe92eff..4c1a3d8e4a41007ade8e32ebf1371c1b55278c32 100644 --- a/app/code/Magento/Paypal/view/adminhtml/templates/transparent/form.phtml +++ b/app/code/Magento/Paypal/view/adminhtml/templates/transparent/form.phtml @@ -129,12 +129,12 @@ $ccExpMonth = $block->getInfoData('cc_exp_month'); <?php endif; ?> <?php if($block->isVaultEnabled()): ?> - <div class="field-tooltip-content"> + <div class="admin__field admin__field-option field-tooltip-content"> <input type="checkbox" id="<?php /* @noEscape */ echo $code; ?>_vault" name="payment[is_active_payment_token_enabler]" class="admin__control-checkbox"/> - <label class="label" for="<?php /* @noEscape */ echo $code; ?>_vault"> + <label class="admin__field-label" for="<?php /* @noEscape */ echo $code; ?>_vault"> <span><?php echo $block->escapeHtml('Save for later use.'); ?></span> </label> </div> diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js index 97dbf0e81b9da0003018ab6c6975aea3216501b4..8a29f0c3a6ea4f774ffc38c9eeb5e65dd34b9657 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js @@ -25,6 +25,9 @@ define( ) { 'use strict'; + // State of PayPal module initialization + var clientInit = false; + return Component.extend({ defaults: { @@ -93,15 +96,26 @@ define( * @returns {Object} */ initClient: function () { + var selector = '#' + this.getButtonId(); + _.each(this.clientConfig, function (fn, name) { if (typeof fn === 'function') { this.clientConfig[name] = fn.bind(this); } }, this); - domObserver.get('#' + this.getButtonId(), function () { - paypalExpressCheckout.checkout.setup(this.merchantId, this.clientConfig); - }.bind(this)); + if (!clientInit) { + domObserver.get(selector, function () { + paypalExpressCheckout.checkout.setup(this.merchantId, this.clientConfig); + clientInit = true; + domObserver.off(selector); + }.bind(this)); + } else { + domObserver.get(selector, function () { + $(selector).on('click', this.clientConfig.click); + domObserver.off(selector); + }.bind(this)); + } return this; }, diff --git a/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php b/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php index 7685fc0fed94df32b0e0638e65d6d15f6de1d5ce..b93cb70fdda3d9cc9b6d8d9f7bb565d576392de4 100644 --- a/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php +++ b/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php @@ -8,6 +8,8 @@ namespace Magento\Review\Test\Unit\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier\AbstractModifierTest; use Magento\Framework\UrlInterface; use Magento\Review\Ui\DataProvider\Product\Form\Modifier\Review; +use Magento\Framework\Module\Manager as ModuleManager; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; /** * Class ReviewTest @@ -19,36 +21,73 @@ class ReviewTest extends AbstractModifierTest */ protected $urlBuilderMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $moduleManagerMock; + protected function setUp() { parent::setUp(); $this->urlBuilderMock = $this->getMockBuilder(UrlInterface::class) ->getMockForAbstractClass(); + $this->moduleManagerMock = $this->getMock(ModuleManager::class, [], [], '', false); } + /** + * @return ModifierInterface + */ protected function createModel() { - return $this->objectManager->getObject(Review::class, [ + $model = $this->objectManager->getObject(Review::class, [ 'locator' => $this->locatorMock, 'urlBuilder' => $this->urlBuilderMock, ]); + + $reviewClass = new \ReflectionClass(Review::class); + $moduleManagerProperty = $reviewClass->getProperty('moduleManager'); + $moduleManagerProperty->setAccessible(true); + $moduleManagerProperty->setValue( + $model, + $this->moduleManagerMock + ); + + return $model; } - public function testModifyMetaToBeEmpty() + public function testModifyMetaDoesNotAddReviewSectionForNewProduct() + { + $this->productMock->expects($this->once()) + ->method('getId'); + + $this->assertSame([], $this->getModel()->modifyMeta([])); + } + + public function testModifyMetaDoesNotAddReviewSectionIfReviewModuleOutputIsDisabled() { $this->productMock->expects($this->once()) ->method('getId') - ->willReturn(0); + ->willReturn(1); + + $this->moduleManagerMock->expects($this->any()) + ->method('isOutputEnabled') + ->with('Magento_Review') + ->willReturn(false); $this->assertSame([], $this->getModel()->modifyMeta([])); } - public function testModifyMeta() + public function testModifyMetaAddsReviewSectionForExistingProductIfReviewModuleOutputIsEnabled() { $this->productMock->expects($this->once()) ->method('getId') ->willReturn(1); + $this->moduleManagerMock->expects($this->any()) + ->method('isOutputEnabled') + ->with('Magento_Review') + ->willReturn(true); + $this->assertArrayHasKey(Review::GROUP_REVIEW, $this->getModel()->modifyMeta([])); } diff --git a/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php b/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php index 82141b5ab2f12a03e44463555e7b0a302630b0d2..0ef1057eb4be7ffd23fe794bef67663dc247b5c6 100644 --- a/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php +++ b/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php @@ -12,6 +12,8 @@ use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Ui\Component\Form; use Magento\Framework\UrlInterface; +use Magento\Framework\Module\Manager as ModuleManager; +use Magento\Framework\App\ObjectManager; /** * Class Review @@ -34,6 +36,11 @@ class Review extends AbstractModifier */ protected $urlBuilder; + /** + * @var ModuleManager + */ + private $moduleManager; + /** * @param LocatorInterface $locator * @param UrlInterface $urlBuilder @@ -51,7 +58,7 @@ class Review extends AbstractModifier */ public function modifyMeta(array $meta) { - if (!$this->locator->getProduct()->getId()) { + if (!$this->locator->getProduct()->getId() || !$this->getModuleManager()->isOutputEnabled('Magento_Review')) { return $meta; } @@ -114,4 +121,19 @@ class Review extends AbstractModifier return $data; } + + /** + * Retrieve module manager instance using dependency lookup to keep this class backward compatible. + * + * @return ModuleManager + * + * @deprecated + */ + private function getModuleManager() + { + if ($this->moduleManager === null) { + $this->moduleManager = ObjectManager::getInstance()->get(ModuleManager::class); + } + return $this->moduleManager; + } } diff --git a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php index 1e1d217715d260e5f43c437b7a91431215d338f1..4b656ee299dddc04527d2be6145b49e3098c3578 100644 --- a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php +++ b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php @@ -602,7 +602,9 @@ abstract class AbstractProduct extends \Magento\Rule\Model\Condition\AbstractCon */ public function getMappedSqlField() { - if (!$this->isAttributeSetOrCategory()) { + if ($this->getAttribute() == 'sku') { + $mappedSqlField = 'e.sku'; + } elseif (!$this->isAttributeSetOrCategory()) { $mappedSqlField = $this->getEavAttributeTableAlias() . '.value'; } elseif ($this->getAttribute() == 'category_ids') { $mappedSqlField = 'e.entity_id'; diff --git a/app/code/Magento/Sales/Model/InvoiceOrder.php b/app/code/Magento/Sales/Model/InvoiceOrder.php index e51b46082d943d8ff609bacb80fae19c861afba4..c503b01a5ab21014683a7eaa5981f65fa16b558f 100644 --- a/app/code/Magento/Sales/Model/InvoiceOrder.php +++ b/app/code/Magento/Sales/Model/InvoiceOrder.php @@ -12,15 +12,12 @@ use Magento\Sales\Api\Data\InvoiceCreationArgumentsInterface; use Magento\Sales\Api\InvoiceOrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order\Config as OrderConfig; -use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; use Magento\Sales\Model\Order\Invoice\NotifierInterface; use Magento\Sales\Model\Order\InvoiceDocumentFactory; -use Magento\Sales\Model\Order\InvoiceQuantityValidator; use Magento\Sales\Model\Order\InvoiceRepository; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\PaymentAdapterInterface; -use Magento\Sales\Model\Order\Validation\CanInvoice; +use Magento\Sales\Model\Order\Validation\InvoiceOrderInterface as InvoiceOrderValidator; use Psr\Log\LoggerInterface; /** @@ -44,11 +41,6 @@ class InvoiceOrder implements InvoiceOrderInterface */ private $invoiceDocumentFactory; - /** - * @var InvoiceValidatorInterface - */ - private $invoiceValidator; - /** * @var PaymentAdapterInterface */ @@ -69,6 +61,11 @@ class InvoiceOrder implements InvoiceOrderInterface */ private $invoiceRepository; + /** + * @var InvoiceOrderValidator + */ + private $invoiceOrderValidator; + /** * @var NotifierInterface */ @@ -80,21 +77,15 @@ class InvoiceOrder implements InvoiceOrderInterface private $logger; /** - * @var OrderValidatorInterface - */ - private $orderValidator; - - /** - * OrderInvoice constructor. + * InvoiceOrder constructor. * @param ResourceConnection $resourceConnection * @param OrderRepositoryInterface $orderRepository * @param InvoiceDocumentFactory $invoiceDocumentFactory - * @param InvoiceValidatorInterface $invoiceValidator - * @param OrderValidatorInterface $orderValidator * @param PaymentAdapterInterface $paymentAdapter * @param OrderStateResolverInterface $orderStateResolver * @param OrderConfig $config * @param InvoiceRepository $invoiceRepository + * @param InvoiceOrderValidator $invoiceOrderValidator * @param NotifierInterface $notifierInterface * @param LoggerInterface $logger * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -103,24 +94,22 @@ class InvoiceOrder implements InvoiceOrderInterface ResourceConnection $resourceConnection, OrderRepositoryInterface $orderRepository, InvoiceDocumentFactory $invoiceDocumentFactory, - InvoiceValidatorInterface $invoiceValidator, - OrderValidatorInterface $orderValidator, PaymentAdapterInterface $paymentAdapter, OrderStateResolverInterface $orderStateResolver, OrderConfig $config, InvoiceRepository $invoiceRepository, + InvoiceOrderValidator $invoiceOrderValidator, NotifierInterface $notifierInterface, LoggerInterface $logger ) { $this->resourceConnection = $resourceConnection; $this->orderRepository = $orderRepository; $this->invoiceDocumentFactory = $invoiceDocumentFactory; - $this->invoiceValidator = $invoiceValidator; - $this->orderValidator = $orderValidator; $this->paymentAdapter = $paymentAdapter; $this->orderStateResolver = $orderStateResolver; $this->config = $config; $this->invoiceRepository = $invoiceRepository; + $this->invoiceOrderValidator = $invoiceOrderValidator; $this->notifierInterface = $notifierInterface; $this->logger = $logger; } @@ -158,19 +147,19 @@ class InvoiceOrder implements InvoiceOrderInterface ($appendComment && $notify), $arguments ); - $errorMessages = array_merge( - $this->invoiceValidator->validate( - $invoice, - [InvoiceQuantityValidator::class] - ), - $this->orderValidator->validate( - $order, - [CanInvoice::class] - ) + $errorMessages = $this->invoiceOrderValidator->validate( + $order, + $invoice, + $capture, + $items, + $notify, + $appendComment, + $comment, + $arguments ); - if (!empty($errorMessages)) { + if ($errorMessages->hasMessages()) { throw new \Magento\Sales\Exception\DocumentValidationException( - __("Invoice Document Validation Error(s):\n" . implode("\n", $errorMessages)) + __("Invoice Document Validation Error(s):\n" . implode("\n", $errorMessages->getMessages())) ); } $connection->beginTransaction(); diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php index 3889f3b985ff02603009084fa956fab65e505556..030a9a7d128de7d49d989c0e1af61a323786f5a5 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/CreditmemoValidatorInterface.php @@ -8,6 +8,7 @@ namespace Magento\Sales\Model\Order\Creditmemo; use Magento\Sales\Api\Data\CreditmemoInterface; use Magento\Sales\Exception\DocumentValidationException; use Magento\Sales\Model\ValidatorInterface; +use Magento\Sales\Model\ValidatorResultInterface; /** * Interface CreditmemoValidatorInterface @@ -17,7 +18,7 @@ interface CreditmemoValidatorInterface /** * @param CreditmemoInterface $entity * @param ValidatorInterface[] $validators - * @return string[] + * @return ValidatorResultInterface * @throws DocumentValidationException */ public function validate(CreditmemoInterface $entity, array $validators); diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php index 9f8bb84ccd16a175ee23de755169281026dc053b..7a758122b8aac13d75dcf5784ef85e71d2298b2d 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreationValidatorInterface.php @@ -7,6 +7,7 @@ namespace Magento\Sales\Model\Order\Creditmemo; use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\ValidatorResultInterface; /** * Interface ItemCreationValidatorInterface @@ -17,7 +18,7 @@ interface ItemCreationValidatorInterface * @param CreditmemoItemCreationInterface $item * @param array $validators * @param OrderInterface|null $context - * @return mixed + * @return ValidatorResultInterface */ public function validate(CreditmemoItemCreationInterface $item, array $validators, OrderInterface $context = null); } diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php index 469b226053cdd4e2b0658dcd596d7d740572f0c7..816c3df3fc1f0c6b045f26f2ebb79e769c1be031 100644 --- a/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php +++ b/app/code/Magento/Sales/Model/Order/CreditmemoDocumentFactory.php @@ -7,6 +7,8 @@ namespace Magento\Sales\Model\Order; /** * Class CreditmemoDocumentFactory + * + * @api */ class CreditmemoDocumentFactory { diff --git a/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php index 568019a40fce5bc69ccab49ca8b3f4fee048505e..44d701b1426e79d3df390b46b7e76ee6743b691c 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php +++ b/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php @@ -8,6 +8,7 @@ namespace Magento\Sales\Model\Order\Invoice; use Magento\Sales\Api\Data\InvoiceInterface; use Magento\Sales\Exception\DocumentValidationException; use Magento\Sales\Model\ValidatorInterface; +use Magento\Sales\Model\ValidatorResultInterface; /** * Interface InvoiceValidatorInterface @@ -17,7 +18,7 @@ interface InvoiceValidatorInterface /** * @param InvoiceInterface $entity * @param ValidatorInterface[] $validators - * @return string[] + * @return ValidatorResultInterface * @throws DocumentValidationException */ public function validate(InvoiceInterface $entity, array $validators); diff --git a/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php b/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php index c5a9a6c1d32963fcc223099efe1fcb81bb530464..dfc95043cbd33b214b46f26e7fcdc2a039e9272e 100644 --- a/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php +++ b/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php @@ -8,6 +8,7 @@ namespace Magento\Sales\Model\Order; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Exception\DocumentValidationException; use Magento\Sales\Model\ValidatorInterface; +use Magento\Sales\Model\ValidatorResultInterface; /** * Interface OrderValidatorInterface @@ -17,7 +18,7 @@ interface OrderValidatorInterface /** * @param OrderInterface $entity * @param ValidatorInterface[] $validators - * @return string[] + * @return ValidatorResultInterface * @throws DocumentValidationException */ public function validate(OrderInterface $entity, array $validators); diff --git a/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php index 198a4019bf6b898e0c85fd2e2ff7c7ff77c2a9f6..43501a5b133140390e539a0d81985a4a5437598b 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php +++ b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php @@ -8,6 +8,7 @@ namespace Magento\Sales\Model\Order\Shipment; use Magento\Sales\Api\Data\ShipmentInterface; use Magento\Sales\Exception\DocumentValidationException; use Magento\Sales\Model\ValidatorInterface; +use Magento\Sales\Model\ValidatorResultInterface; /** * Interface ShipmentValidatorInterface @@ -17,7 +18,7 @@ interface ShipmentValidatorInterface /** * @param ShipmentInterface $shipment * @param ValidatorInterface[] $validators - * @return string[] + * @return ValidatorResultInterface * @throws DocumentValidationException */ public function validate(ShipmentInterface $shipment, array $validators); diff --git a/app/code/Magento/Sales/Model/Order/Validation/InvoiceOrder.php b/app/code/Magento/Sales/Model/Order/Validation/InvoiceOrder.php new file mode 100644 index 0000000000000000000000000000000000000000..d912793afa157fbbd997e89ce46c8b84aa5d6f75 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/InvoiceOrder.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\InvoiceCommentCreationInterface; +use Magento\Sales\Api\Data\InvoiceCreationArgumentsInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; +use Magento\Sales\Model\Order\InvoiceQuantityValidator; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\ValidatorResultInterface; +use Magento\Sales\Model\ValidatorResultMerger; + +/** + * Class InvoiceOrder + * Validation for invoice order operation + */ +class InvoiceOrder implements InvoiceOrderInterface +{ + /** + * @var InvoiceValidatorInterface + */ + private $invoiceValidator; + + /** + * @var OrderValidatorInterface + */ + private $orderValidator; + + /** + * @var ValidatorResultMerger + */ + private $validatorResultMerger; + + /** + * InvoiceOrder constructor. + * @param InvoiceValidatorInterface $invoiceValidator + * @param OrderValidatorInterface $orderValidator + * @param ValidatorResultMerger $validatorResultMerger + */ + public function __construct( + InvoiceValidatorInterface $invoiceValidator, + OrderValidatorInterface $orderValidator, + ValidatorResultMerger $validatorResultMerger + ) { + $this->invoiceValidator = $invoiceValidator; + $this->orderValidator = $orderValidator; + $this->validatorResultMerger = $validatorResultMerger; + } + + /** + * @inheritdoc + */ + public function validate( + OrderInterface $order, + InvoiceInterface $invoice, + $capture = false, + array $items = [], + $notify = false, + $appendComment = false, + InvoiceCommentCreationInterface $comment = null, + InvoiceCreationArgumentsInterface $arguments = null + ) { + return $this->validatorResultMerger->merge( + $this->invoiceValidator->validate( + $invoice, + [InvoiceQuantityValidator::class] + ), + $this->orderValidator->validate( + $order, + [CanInvoice::class] + ) + ); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/InvoiceOrderInterface.php b/app/code/Magento/Sales/Model/Order/Validation/InvoiceOrderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5c27741a1985582ee547a21faf9747d68d2c35c4 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/InvoiceOrderInterface.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\InvoiceCommentCreationInterface; +use Magento\Sales\Api\Data\InvoiceCreationArgumentsInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\ValidatorResultInterface; + +/** + * Interface InvoiceOrderInterface + * + * @api + */ +interface InvoiceOrderInterface +{ + /** + * @param OrderInterface $order + * @param InvoiceInterface $invoice + * @param bool $capture + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @param InvoiceCommentCreationInterface|null $comment + * @param InvoiceCreationArgumentsInterface|null $arguments + * @return ValidatorResultInterface + */ + public function validate( + OrderInterface $order, + InvoiceInterface $invoice, + $capture = false, + array $items = [], + $notify = false, + $appendComment = false, + InvoiceCommentCreationInterface $comment = null, + InvoiceCreationArgumentsInterface $arguments = null + ); +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/RefundInvoice.php b/app/code/Magento/Sales/Model/Order/Validation/RefundInvoice.php new file mode 100644 index 0000000000000000000000000000000000000000..d6bc86005cf2c906154ca107cc97c4fac4242fd4 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/RefundInvoice.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\ValidatorResultMerger; + +/** + * Class RefundInvoice + */ +class RefundInvoice implements RefundInvoiceInterface +{ + /** + * @var OrderValidatorInterface + */ + private $orderValidator; + + /** + * @var CreditmemoValidatorInterface + */ + private $creditmemoValidator; + + /** + * @var ItemCreationValidatorInterface + */ + private $itemCreationValidator; + + /** + * @var InvoiceValidatorInterface + */ + private $invoiceValidator; + + /** + * @var ValidatorResultMerger + */ + private $validatorResultMerger; + + /** + * RefundArguments constructor. + * @param OrderValidatorInterface $orderValidator + * @param CreditmemoValidatorInterface $creditmemoValidator + * @param ItemCreationValidatorInterface $itemCreationValidator + * @param InvoiceValidatorInterface $invoiceValidator + * @param ValidatorResultMerger $validatorResultMerger + */ + public function __construct( + OrderValidatorInterface $orderValidator, + CreditmemoValidatorInterface $creditmemoValidator, + ItemCreationValidatorInterface $itemCreationValidator, + InvoiceValidatorInterface $invoiceValidator, + ValidatorResultMerger $validatorResultMerger + ) { + $this->orderValidator = $orderValidator; + $this->creditmemoValidator = $creditmemoValidator; + $this->itemCreationValidator = $itemCreationValidator; + $this->invoiceValidator = $invoiceValidator; + $this->validatorResultMerger = $validatorResultMerger; + } + + /** + * @inheritdoc + */ + public function validate( + InvoiceInterface $invoice, + OrderInterface $order, + CreditmemoInterface $creditmemo, + array $items = [], + $isOnline = false, + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $orderValidationResult = $this->orderValidator->validate( + $order, + [ + CanRefund::class + ] + ); + $creditmemoValidationResult = $this->creditmemoValidator->validate( + $creditmemo, + [ + QuantityValidator::class, + TotalsValidator::class + ] + ); + + $itemsValidation = []; + foreach ($items as $item) { + $itemsValidation[] = $this->itemCreationValidator->validate( + $item, + [CreationQuantityValidator::class], + $order + )->getMessages(); + } + + $invoiceValidationResult = $this->invoiceValidator->validate( + $invoice, + [ + \Magento\Sales\Model\Order\Invoice\Validation\CanRefund::class + ] + ); + + return $this->validatorResultMerger->merge( + $orderValidationResult, + $creditmemoValidationResult, + $invoiceValidationResult->getMessages(), + ...$itemsValidation + ); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/RefundInvoiceInterface.php b/app/code/Magento/Sales/Model/Order/Validation/RefundInvoiceInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..83acc9811bb89f5ed8cff566274eb54a39668ad9 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/RefundInvoiceInterface.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\ValidatorResultInterface; + +/** + * Interface RefundInvoiceInterface + * + * @api + */ +interface RefundInvoiceInterface +{ + /** + * @param InvoiceInterface $invoice + * @param OrderInterface $order + * @param CreditmemoInterface $creditmemo + * @param array $items + * @param bool $isOnline + * @param bool $notify + * @param bool $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return ValidatorResultInterface + */ + public function validate( + InvoiceInterface $invoice, + OrderInterface $order, + CreditmemoInterface $creditmemo, + array $items = [], + $isOnline = false, + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ); +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/RefundOrder.php b/app/code/Magento/Sales/Model/Order/Validation/RefundOrder.php new file mode 100644 index 0000000000000000000000000000000000000000..c2664fda6b9836df6c91a35aabd6c84ed54e5827 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/RefundOrder.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; +use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; +use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\ValidatorResultMerger; + +/** + * Class RefundOrder + */ +class RefundOrder implements RefundOrderInterface +{ + /** + * @var OrderValidatorInterface + */ + private $orderValidator; + + /** + * @var CreditmemoValidatorInterface + */ + private $creditmemoValidator; + + /** + * @var ItemCreationValidatorInterface + */ + private $itemCreationValidator; + + /** + * @var ValidatorResultMerger + */ + private $validatorResultMerger; + + /** + * RefundArguments constructor. + * + * @param OrderValidatorInterface $orderValidator + * @param CreditmemoValidatorInterface $creditmemoValidator + * @param ItemCreationValidatorInterface $itemCreationValidator + * @param ValidatorResultMerger $validatorResultMerger + */ + public function __construct( + OrderValidatorInterface $orderValidator, + CreditmemoValidatorInterface $creditmemoValidator, + ItemCreationValidatorInterface $itemCreationValidator, + ValidatorResultMerger $validatorResultMerger + ) { + $this->orderValidator = $orderValidator; + $this->creditmemoValidator = $creditmemoValidator; + $this->itemCreationValidator = $itemCreationValidator; + $this->validatorResultMerger = $validatorResultMerger; + } + + /** + * @inheritdoc + */ + public function validate( + OrderInterface $order, + CreditmemoInterface $creditmemo, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + $orderValidationResult = $this->orderValidator->validate( + $order, + [ + CanRefund::class + ] + ); + $creditmemoValidationResult = $this->creditmemoValidator->validate( + $creditmemo, + [ + QuantityValidator::class, + TotalsValidator::class + ] + ); + + $itemsValidation = []; + foreach ($items as $item) { + $itemsValidation[] = $this->itemCreationValidator->validate( + $item, + [CreationQuantityValidator::class], + $order + )->getMessages(); + } + + return $this->validatorResultMerger->merge( + $orderValidationResult, + $creditmemoValidationResult, + ...$itemsValidation + ); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/RefundOrderInterface.php b/app/code/Magento/Sales/Model/Order/Validation/RefundOrderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2f770d20b513425dd67303490d67bb3d05fbc515 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/RefundOrderInterface.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\ValidatorResultInterface; + +/** + * Interface RefundOrderInterface + * + * @api + */ +interface RefundOrderInterface +{ + /** + * @param OrderInterface $order + * @param CreditmemoInterface $creditmemo + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return ValidatorResultInterface + */ + public function validate( + OrderInterface $order, + CreditmemoInterface $creditmemo, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ); +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php b/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php new file mode 100644 index 0000000000000000000000000000000000000000..984c5f26fa7dbe50234b6f2b075397fb40f62ca1 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/ShipOrder.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\ShipmentInterface; +use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator; +use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface; +use Magento\Sales\Model\Order\Shipment\Validation\TrackValidator; +use Magento\Sales\Model\ValidatorResultMerger; + +/** + * Class ShipOrder + */ +class ShipOrder implements ShipOrderInterface +{ + /** + * @var OrderValidatorInterface + */ + private $orderValidator; + + /** + * @var ShipmentValidatorInterface + */ + private $shipmentValidator; + + /** + * @var ValidatorResultMerger + */ + private $validatorResultMerger; + + /** + * ShipOrder constructor. + * + * @param OrderValidatorInterface $orderValidator + * @param ShipmentValidatorInterface $shipmentValidator + * @param ValidatorResultMerger $validatorResultMerger + */ + public function __construct( + OrderValidatorInterface $orderValidator, + ShipmentValidatorInterface $shipmentValidator, + ValidatorResultMerger $validatorResultMerger + ) { + $this->orderValidator = $orderValidator; + $this->shipmentValidator = $shipmentValidator; + $this->validatorResultMerger = $validatorResultMerger; + } + + /** + * @param OrderInterface $order + * @param ShipmentInterface $shipment + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @param \Magento\Sales\Api\Data\ShipmentCommentCreationInterface|null $comment + * @param array $tracks + * @param array $packages + * @param \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface|null $arguments + * @return \Magento\Sales\Model\ValidatorResultInterface + */ + public function validate( + $order, + $shipment, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null, + array $tracks = [], + array $packages = [], + \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface $arguments = null + ) { + $orderValidationResult = $this->orderValidator->validate( + $order, + [ + CanShip::class + ] + ); + $shipmentValidationResult = $this->shipmentValidator->validate( + $shipment, + [ + QuantityValidator::class, + TrackValidator::class + ] + ); + + return $this->validatorResultMerger->merge($orderValidationResult, $shipmentValidationResult); + } +} diff --git a/app/code/Magento/Sales/Model/Order/Validation/ShipOrderInterface.php b/app/code/Magento/Sales/Model/Order/Validation/ShipOrderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..43f12df445b799621d220182bc009ff02f74c1bb --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/Validation/ShipOrderInterface.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Validation; + +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\ShipmentInterface; + +/** + * Interface ShipOrderInterface + * + * @api + */ +interface ShipOrderInterface +{ + /** + * @param OrderInterface $order + * @param ShipmentInterface $shipment + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @param \Magento\Sales\Api\Data\ShipmentCommentCreationInterface|null $comment + * @param array $tracks + * @param array $packages + * @param \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface|null $arguments + * @return \Magento\Sales\Model\ValidatorResultInterface + */ + public function validate( + $order, + $shipment, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null, + array $tracks = [], + array $packages = [], + \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface $arguments = null + ); +} diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index 5c14b3fd4433e4404db3921a1fffe5b13935643f..f0477fd76014adae7fefa22c6c16966819856266 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -146,6 +146,16 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface */ public function save(\Magento\Sales\Api\Data\OrderInterface $entity) { + /** @var \Magento\Sales\Api\Data\OrderExtensionInterface $extensionAttributes */ + $extensionAttributes = $entity->getExtensionAttributes(); + if ($entity->getIsNotVirtual() && $extensionAttributes && $extensionAttributes->getShippingAssignments()) { + $shippingAssignments = $extensionAttributes->getShippingAssignments(); + if (!empty($shippingAssignments)) { + $shipping = array_shift($shippingAssignments)->getShipping(); + $entity->setShippingAddress($shipping->getAddress()); + $entity->setShippingMethod($shipping->getMethod()); + } + } $this->metadata->getMapper()->save($entity); $this->registry[$entity->getEntityId()] = $entity; return $this->registry[$entity->getEntityId()]; diff --git a/app/code/Magento/Sales/Model/RefundInvoice.php b/app/code/Magento/Sales/Model/RefundInvoice.php index ad9f89b29bff8e18b579ac17c79da4286dc7aa84..60c3a2ac121254591cdc171433770d6c2d539e80 100644 --- a/app/code/Magento/Sales/Model/RefundInvoice.php +++ b/app/code/Magento/Sales/Model/RefundInvoice.php @@ -11,18 +11,11 @@ use Magento\Sales\Api\InvoiceRepositoryInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Api\RefundInvoiceInterface; use Magento\Sales\Model\Order\Config as OrderConfig; -use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; -use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; -use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; -use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; -use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator; use Magento\Sales\Model\Order\CreditmemoDocumentFactory; -use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; +use Magento\Sales\Model\Order\Validation\RefundInvoiceInterface as RefundInvoiceValidator; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\PaymentAdapterInterface; -use Magento\Sales\Model\Order\Validation\CanRefund; use Psr\Log\LoggerInterface; /** @@ -51,33 +44,13 @@ class RefundInvoice implements RefundInvoiceInterface */ private $invoiceRepository; - /** - * @var OrderValidatorInterface - */ - private $orderValidator; - - /** - * @var InvoiceValidatorInterface - */ - private $invoiceValidator; - - /** - * @var CreditmemoValidatorInterface - */ - private $creditmemoValidator; - - /** - * @var ItemCreationValidatorInterface - */ - private $itemCreationValidator; - /** * @var CreditmemoRepositoryInterface */ private $creditmemoRepository; /** - * @var Order\PaymentAdapterInterface + * @var PaymentAdapterInterface */ private $paymentAdapter; @@ -87,7 +60,7 @@ class RefundInvoice implements RefundInvoiceInterface private $creditmemoDocumentFactory; /** - * @var Order\Creditmemo\NotifierInterface + * @var NotifierInterface */ private $notifier; @@ -101,6 +74,11 @@ class RefundInvoice implements RefundInvoiceInterface */ private $logger; + /** + * @var RefundInvoiceValidator + */ + private $validator; + /** * RefundInvoice constructor. * @@ -108,10 +86,7 @@ class RefundInvoice implements RefundInvoiceInterface * @param OrderStateResolverInterface $orderStateResolver * @param OrderRepositoryInterface $orderRepository * @param InvoiceRepositoryInterface $invoiceRepository - * @param OrderValidatorInterface $orderValidator - * @param InvoiceValidatorInterface $invoiceValidator - * @param CreditmemoValidatorInterface $creditmemoValidator - * @param Order\Creditmemo\ItemCreationValidatorInterface $itemCreationValidator + * @param RefundInvoiceValidator $validator * @param CreditmemoRepositoryInterface $creditmemoRepository * @param PaymentAdapterInterface $paymentAdapter * @param CreditmemoDocumentFactory $creditmemoDocumentFactory @@ -125,10 +100,7 @@ class RefundInvoice implements RefundInvoiceInterface OrderStateResolverInterface $orderStateResolver, OrderRepositoryInterface $orderRepository, InvoiceRepositoryInterface $invoiceRepository, - OrderValidatorInterface $orderValidator, - InvoiceValidatorInterface $invoiceValidator, - CreditmemoValidatorInterface $creditmemoValidator, - ItemCreationValidatorInterface $itemCreationValidator, + RefundInvoiceValidator $validator, CreditmemoRepositoryInterface $creditmemoRepository, PaymentAdapterInterface $paymentAdapter, CreditmemoDocumentFactory $creditmemoDocumentFactory, @@ -140,16 +112,13 @@ class RefundInvoice implements RefundInvoiceInterface $this->orderStateResolver = $orderStateResolver; $this->orderRepository = $orderRepository; $this->invoiceRepository = $invoiceRepository; - $this->orderValidator = $orderValidator; - $this->creditmemoValidator = $creditmemoValidator; - $this->itemCreationValidator = $itemCreationValidator; + $this->validator = $validator; $this->creditmemoRepository = $creditmemoRepository; $this->paymentAdapter = $paymentAdapter; $this->creditmemoDocumentFactory = $creditmemoDocumentFactory; $this->notifier = $notifier; $this->config = $config; $this->logger = $logger; - $this->invoiceValidator = $invoiceValidator; } /** @@ -174,45 +143,21 @@ class RefundInvoice implements RefundInvoiceInterface ($appendComment && $notify), $arguments ); - $orderValidationResult = $this->orderValidator->validate( - $order, - [ - CanRefund::class - ] - ); - $invoiceValidationResult = $this->invoiceValidator->validate( + + $validationMessages = $this->validator->validate( $invoice, - [ - \Magento\Sales\Model\Order\Invoice\Validation\CanRefund::class - ] - ); - $creditmemoValidationResult = $this->creditmemoValidator->validate( + $order, $creditmemo, - [ - QuantityValidator::class, - TotalsValidator::class - ] - ); - $itemsValidation = []; - foreach ($items as $item) { - $itemsValidation = array_merge( - $itemsValidation, - $this->itemCreationValidator->validate( - $item, - [CreationQuantityValidator::class], - $order - ) - ); - } - $validationMessages = array_merge( - $orderValidationResult, - $invoiceValidationResult, - $creditmemoValidationResult, - $itemsValidation + $items, + $isOnline, + $notify, + $appendComment, + $comment, + $arguments ); - if (!empty($validationMessages )) { + if ($validationMessages->hasMessages()) { throw new \Magento\Sales\Exception\DocumentValidationException( - __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages)) + __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages->getMessages())) ); } $connection->beginTransaction(); diff --git a/app/code/Magento/Sales/Model/RefundOrder.php b/app/code/Magento/Sales/Model/RefundOrder.php index 4cfbe063ea81f29e7a6608c0c04fec269326c44c..abd6e25416729b3b995ba92425cbc3c3c7de8943 100644 --- a/app/code/Magento/Sales/Model/RefundOrder.php +++ b/app/code/Magento/Sales/Model/RefundOrder.php @@ -10,17 +10,11 @@ use Magento\Sales\Api\CreditmemoRepositoryInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Api\RefundOrderInterface; use Magento\Sales\Model\Order\Config as OrderConfig; -use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; -use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; -use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; -use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator; -use Magento\Sales\Model\Order\Creditmemo\Validation\TotalsValidator; use Magento\Sales\Model\Order\CreditmemoDocumentFactory; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\PaymentAdapterInterface; -use Magento\Sales\Model\Order\Validation\CanRefund; +use Magento\Sales\Model\Order\Validation\RefundOrderInterface as RefundOrderValidator; use Psr\Log\LoggerInterface; /** @@ -44,28 +38,13 @@ class RefundOrder implements RefundOrderInterface */ private $orderRepository; - /** - * @var OrderValidatorInterface - */ - private $orderValidator; - - /** - * @var CreditmemoValidatorInterface - */ - private $creditmemoValidator; - - /** - * @var Order\Creditmemo\ItemCreationValidatorInterface - */ - private $itemCreationValidator; - /** * @var CreditmemoRepositoryInterface */ private $creditmemoRepository; /** - * @var Order\PaymentAdapterInterface + * @var PaymentAdapterInterface */ private $paymentAdapter; @@ -75,7 +54,12 @@ class RefundOrder implements RefundOrderInterface private $creditmemoDocumentFactory; /** - * @var Order\Creditmemo\NotifierInterface + * @var RefundOrderValidator + */ + private $validator; + + /** + * @var NotifierInterface */ private $notifier; @@ -91,15 +75,14 @@ class RefundOrder implements RefundOrderInterface /** * RefundOrder constructor. + * * @param ResourceConnection $resourceConnection * @param OrderStateResolverInterface $orderStateResolver * @param OrderRepositoryInterface $orderRepository - * @param OrderValidatorInterface $orderValidator - * @param CreditmemoValidatorInterface $creditmemoValidator - * @param ItemCreationValidatorInterface $itemCreationValidator * @param CreditmemoRepositoryInterface $creditmemoRepository * @param PaymentAdapterInterface $paymentAdapter * @param CreditmemoDocumentFactory $creditmemoDocumentFactory + * @param RefundOrderValidator $validator * @param NotifierInterface $notifier * @param OrderConfig $config * @param LoggerInterface $logger @@ -109,12 +92,10 @@ class RefundOrder implements RefundOrderInterface ResourceConnection $resourceConnection, OrderStateResolverInterface $orderStateResolver, OrderRepositoryInterface $orderRepository, - OrderValidatorInterface $orderValidator, - CreditmemoValidatorInterface $creditmemoValidator, - ItemCreationValidatorInterface $itemCreationValidator, CreditmemoRepositoryInterface $creditmemoRepository, PaymentAdapterInterface $paymentAdapter, CreditmemoDocumentFactory $creditmemoDocumentFactory, + RefundOrderValidator $validator, NotifierInterface $notifier, OrderConfig $config, LoggerInterface $logger @@ -122,12 +103,10 @@ class RefundOrder implements RefundOrderInterface $this->resourceConnection = $resourceConnection; $this->orderStateResolver = $orderStateResolver; $this->orderRepository = $orderRepository; - $this->orderValidator = $orderValidator; - $this->creditmemoValidator = $creditmemoValidator; - $this->itemCreationValidator = $itemCreationValidator; $this->creditmemoRepository = $creditmemoRepository; $this->paymentAdapter = $paymentAdapter; $this->creditmemoDocumentFactory = $creditmemoDocumentFactory; + $this->validator = $validator; $this->notifier = $notifier; $this->config = $config; $this->logger = $logger; @@ -153,34 +132,18 @@ class RefundOrder implements RefundOrderInterface ($appendComment && $notify), $arguments ); - $orderValidationResult = $this->orderValidator->validate( + $validationMessages = $this->validator->validate( $order, - [ - CanRefund::class - ] - ); - $creditmemoValidationResult = $this->creditmemoValidator->validate( $creditmemo, - [ - QuantityValidator::class, - TotalsValidator::class - ] + $items, + $notify, + $appendComment, + $comment, + $arguments ); - $itemsValidation = []; - foreach ($items as $item) { - $itemsValidation = array_merge( - $itemsValidation, - $this->itemCreationValidator->validate( - $item, - [CreationQuantityValidator::class], - $order - ) - ); - } - $validationMessages = array_merge($orderValidationResult, $creditmemoValidationResult, $itemsValidation); - if (!empty($validationMessages)) { + if ($validationMessages->hasMessages()) { throw new \Magento\Sales\Exception\DocumentValidationException( - __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages)) + __("Creditmemo Document Validation Error(s):\n" . implode("\n", $validationMessages->getMessages())) ); } $connection->beginTransaction(); diff --git a/app/code/Magento/Sales/Model/ShipOrder.php b/app/code/Magento/Sales/Model/ShipOrder.php index d051144cf73ca166c30ebd6eb9f626306b1720a6..034442a19c1f7e297e364e894ef53275574c4706 100644 --- a/app/code/Magento/Sales/Model/ShipOrder.php +++ b/app/code/Magento/Sales/Model/ShipOrder.php @@ -11,14 +11,10 @@ use Magento\Sales\Api\ShipmentRepositoryInterface; use Magento\Sales\Api\ShipOrderInterface; use Magento\Sales\Model\Order\Config as OrderConfig; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\ShipmentDocumentFactory; use Magento\Sales\Model\Order\Shipment\NotifierInterface; -use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface; -use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator; -use Magento\Sales\Model\Order\Shipment\Validation\TrackValidator; -use Magento\Sales\Model\Order\Validation\CanShip; use Magento\Sales\Model\Order\Shipment\OrderRegistrarInterface; +use Magento\Sales\Model\Order\Validation\ShipOrderInterface as ShipOrderValidator; use Psr\Log\LoggerInterface; /** @@ -42,11 +38,6 @@ class ShipOrder implements ShipOrderInterface */ private $shipmentDocumentFactory; - /** - * @var ShipmentValidatorInterface - */ - private $shipmentValidator; - /** * @var OrderStateResolverInterface */ @@ -62,6 +53,11 @@ class ShipOrder implements ShipOrderInterface */ private $shipmentRepository; + /** + * @var ShipOrderValidator + */ + private $shipOrderValidator; + /** * @var NotifierInterface */ @@ -72,11 +68,6 @@ class ShipOrder implements ShipOrderInterface */ private $logger; - /** - * @var OrderValidatorInterface - */ - private $orderValidator; - /** * @var OrderRegistrarInterface */ @@ -86,11 +77,10 @@ class ShipOrder implements ShipOrderInterface * @param ResourceConnection $resourceConnection * @param OrderRepositoryInterface $orderRepository * @param ShipmentDocumentFactory $shipmentDocumentFactory - * @param ShipmentValidatorInterface $shipmentValidator - * @param OrderValidatorInterface $orderValidator * @param OrderStateResolverInterface $orderStateResolver * @param OrderConfig $config * @param ShipmentRepositoryInterface $shipmentRepository + * @param ShipOrderValidator $shipOrderValidator * @param NotifierInterface $notifierInterface * @param OrderRegistrarInterface $orderRegistrar * @param LoggerInterface $logger @@ -100,11 +90,10 @@ class ShipOrder implements ShipOrderInterface ResourceConnection $resourceConnection, OrderRepositoryInterface $orderRepository, ShipmentDocumentFactory $shipmentDocumentFactory, - ShipmentValidatorInterface $shipmentValidator, - OrderValidatorInterface $orderValidator, OrderStateResolverInterface $orderStateResolver, OrderConfig $config, ShipmentRepositoryInterface $shipmentRepository, + ShipOrderValidator $shipOrderValidator, NotifierInterface $notifierInterface, OrderRegistrarInterface $orderRegistrar, LoggerInterface $logger @@ -112,11 +101,10 @@ class ShipOrder implements ShipOrderInterface $this->resourceConnection = $resourceConnection; $this->orderRepository = $orderRepository; $this->shipmentDocumentFactory = $shipmentDocumentFactory; - $this->shipmentValidator = $shipmentValidator; - $this->orderValidator = $orderValidator; $this->orderStateResolver = $orderStateResolver; $this->config = $config; $this->shipmentRepository = $shipmentRepository; + $this->shipOrderValidator = $shipOrderValidator; $this->notifierInterface = $notifierInterface; $this->logger = $logger; $this->orderRegistrar = $orderRegistrar; @@ -159,23 +147,19 @@ class ShipOrder implements ShipOrderInterface $packages, $arguments ); - $orderValidationResult = $this->orderValidator->validate( + $validationMessages = $this->shipOrderValidator->validate( $order, - [ - CanShip::class - ] - ); - $shipmentValidationResult = $this->shipmentValidator->validate( $shipment, - [ - QuantityValidator::class, - TrackValidator::class - ] + $items, + $notify, + $appendComment, + $comment, + $tracks, + $packages ); - $validationMessages = array_merge($orderValidationResult, $shipmentValidationResult); - if (!empty($validationMessages)) { + if ($validationMessages->hasMessages()) { throw new \Magento\Sales\Exception\DocumentValidationException( - __("Shipment Document Validation Error(s):\n" . implode("\n", $validationMessages)) + __("Shipment Document Validation Error(s):\n" . implode("\n", $validationMessages->getMessages())) ); } $connection->beginTransaction(); diff --git a/app/code/Magento/Sales/Model/Validator.php b/app/code/Magento/Sales/Model/Validator.php index 10aa0735b952e99e0d889b27ff378411d0d1d6d1..9239223fe3b4a84ee1e319e6f067ca9ba6cfb3e8 100644 --- a/app/code/Magento/Sales/Model/Validator.php +++ b/app/code/Magento/Sales/Model/Validator.php @@ -20,21 +20,30 @@ class Validator */ private $objectManager; + /** + * @var ValidatorResultInterfaceFactory + */ + private $validatorResultFactory; + /** * Validator constructor. * * @param ObjectManagerInterface $objectManager + * @param ValidatorResultInterfaceFactory $validatorResult */ - public function __construct(ObjectManagerInterface $objectManager) - { + public function __construct( + ObjectManagerInterface $objectManager, + ValidatorResultInterfaceFactory $validatorResult + ) { $this->objectManager = $objectManager; + $this->validatorResultFactory = $validatorResult; } /** * @param object $entity * @param ValidatorInterface[] $validators * @param object|null $context - * @return \string[] + * @return ValidatorResultInterface * @throws ConfigurationMismatchException */ public function validate($entity, array $validators, $context = null) @@ -56,7 +65,11 @@ class Validator } $messages = array_merge($messages, $validator->validate($entity)); } + $validationResult = $this->validatorResultFactory->create(); + foreach ($messages as $message) { + $validationResult->addMessage($message); + } - return $messages; + return $validationResult; } } diff --git a/app/code/Magento/Sales/Model/ValidatorResult.php b/app/code/Magento/Sales/Model/ValidatorResult.php new file mode 100644 index 0000000000000000000000000000000000000000..6dc2933ac42a7c2974d397aebf3e3c599831b5bf --- /dev/null +++ b/app/code/Magento/Sales/Model/ValidatorResult.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model; + +/** + * Class ValidatorResult + */ +class ValidatorResult implements ValidatorResultInterface +{ + /** + * @var \string[] + */ + private $messages = []; + + /** + * @inheritdoc + */ + public function addMessage($message) + { + $this->messages[] = (string)$message; + } + + /** + * @return bool + */ + public function hasMessages() + { + return count($this->messages) > 0; + } + + /** + * @return \string[] + */ + public function getMessages() + { + return $this->messages; + } +} diff --git a/app/code/Magento/Sales/Model/ValidatorResultInterface.php b/app/code/Magento/Sales/Model/ValidatorResultInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c4e284657bdd1f1261094f05337577ebf3656ad7 --- /dev/null +++ b/app/code/Magento/Sales/Model/ValidatorResultInterface.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model; + +/** + * Interface ValidatorResultInterface + * @api + */ +interface ValidatorResultInterface +{ + /** + * @param string $message + * @return void + */ + public function addMessage($message); + + /** + * @return bool + */ + public function hasMessages(); + + /** + * @return \string[] + */ + public function getMessages(); +} diff --git a/app/code/Magento/Sales/Model/ValidatorResultMerger.php b/app/code/Magento/Sales/Model/ValidatorResultMerger.php new file mode 100644 index 0000000000000000000000000000000000000000..41f38e8eef078b221c70cdb9520f6043ccfe4daf --- /dev/null +++ b/app/code/Magento/Sales/Model/ValidatorResultMerger.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model; + +/** + * Class ValidatorResultMerger + */ +class ValidatorResultMerger +{ + /** + * @var ValidatorResultInterfaceFactory + */ + private $validatorResultInterfaceFactory; + + /** + * ValidatorResultMerger constructor. + * + * @param ValidatorResultInterfaceFactory $validatorResultInterfaceFactory + */ + public function __construct(ValidatorResultInterfaceFactory $validatorResultInterfaceFactory) + { + $this->validatorResultInterfaceFactory = $validatorResultInterfaceFactory; + } + + /** + * Merge two validator results and additional messages + * + * @param ValidatorResultInterface $first + * @param ValidatorResultInterface $second + * @param \string[] $validatorMessages + * @return ValidatorResultInterface + */ + public function merge(ValidatorResultInterface $first, ValidatorResultInterface $second, ... $validatorMessages) + { + $messages = array_merge($first->getMessages(), $second->getMessages(), ...$validatorMessages); + + $result = $this->validatorResultInterfaceFactory->create(); + foreach ($messages as $message) { + $result->addMessage($message); + } + + return $result; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/InvoiceOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/InvoiceOrderTest.php index 6dfa929acb6290e0e95bf2011b76ec9811691ecc..1169e230e75421d150a2719edfb14c09fddf1120 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/InvoiceOrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/InvoiceOrderTest.php @@ -14,19 +14,19 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Config as OrderConfig; -use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; use Magento\Sales\Model\Order\Invoice\NotifierInterface; use Magento\Sales\Model\Order\InvoiceDocumentFactory; use Magento\Sales\Model\Order\InvoiceRepository; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\Validation\InvoiceOrderInterface; use Magento\Sales\Model\Order\PaymentAdapterInterface; +use Magento\Sales\Model\ValidatorResultInterface; use Magento\Sales\Model\InvoiceOrder; use Psr\Log\LoggerInterface; /** * Class InvoiceOrderTest - * + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -48,14 +48,9 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase private $invoiceDocumentFactoryMock; /** - * @var InvoiceValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var InvoiceOrderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $invoiceValidatorMock; - - /** - * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $orderValidatorMock; + private $invoiceOrderValidatorMock; /** * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject @@ -117,6 +112,11 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase */ private $loggerMock; + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $errorMessagesMock; + protected function setUp() { $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) @@ -131,14 +131,6 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $this->invoiceValidatorMock = $this->getMockBuilder(InvoiceValidatorInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -183,22 +175,37 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); + $this->invoiceOrderValidatorMock = $this->getMockBuilder(InvoiceOrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->errorMessagesMock = $this->getMockBuilder(ValidatorResultInterface::class) + ->disableOriginalConstructor() + ->setMethods(['hasMessages', 'getMessages', 'addMessage']) + ->getMock(); + $this->invoiceOrder = new InvoiceOrder( $this->resourceConnectionMock, $this->orderRepositoryMock, $this->invoiceDocumentFactoryMock, - $this->invoiceValidatorMock, - $this->orderValidatorMock, $this->paymentAdapterMock, $this->orderStateResolverMock, $this->configMock, $this->invoiceRepositoryMock, + $this->invoiceOrderValidatorMock, $this->notifierInterfaceMock, $this->loggerMock ); } /** + * @param int $orderId + * @param bool $capture + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @throws \Magento\Sales\Exception\CouldNotInvoiceException + * @throws \Magento\Sales\Exception\DocumentValidationException * @dataProvider dataProvider */ public function testOrderInvoice($orderId, $capture, $items, $notify, $appendComment) @@ -207,11 +214,9 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase ->method('getConnection') ->with('sales') ->willReturn($this->adapterInterface); - $this->orderRepositoryMock->expects($this->once()) ->method('get') ->willReturn($this->orderMock); - $this->invoiceDocumentFactoryMock->expects($this->once()) ->method('create') ->with( @@ -221,66 +226,62 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase ($appendComment && $notify), $this->invoiceCreationArgumentsMock )->willReturn($this->invoiceMock); - - $this->invoiceValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->invoiceMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) + $this->invoiceOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - + ->with( + $this->orderMock, + $this->invoiceMock, + $capture, + $items, + $notify, + $appendComment, + $this->invoiceCommentCreationMock, + $this->invoiceCreationArgumentsMock + ) + ->willReturn($this->errorMessagesMock); + $hasMessages = false; + $this->errorMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); $this->paymentAdapterMock->expects($this->once()) ->method('pay') ->with($this->orderMock, $this->invoiceMock, $capture) ->willReturn($this->orderMock); - $this->orderStateResolverMock->expects($this->once()) ->method('getStateForOrder') ->with($this->orderMock, [OrderStateResolverInterface::IN_PROGRESS]) ->willReturn(Order::STATE_PROCESSING); - $this->orderMock->expects($this->once()) ->method('setState') ->with(Order::STATE_PROCESSING) ->willReturnSelf(); - $this->orderMock->expects($this->once()) ->method('getState') ->willReturn(Order::STATE_PROCESSING); - $this->configMock->expects($this->once()) ->method('getStateDefaultStatus') ->with(Order::STATE_PROCESSING) ->willReturn('Processing'); - $this->orderMock->expects($this->once()) ->method('setStatus') ->with('Processing') ->willReturnSelf(); - $this->invoiceMock->expects($this->once()) ->method('setState') ->with(\Magento\Sales\Model\Order\Invoice::STATE_PAID) ->willReturnSelf(); - $this->invoiceRepositoryMock->expects($this->once()) ->method('save') ->with($this->invoiceMock) ->willReturn($this->invoiceMock); - $this->orderRepositoryMock->expects($this->once()) ->method('save') ->with($this->orderMock) ->willReturn($this->orderMock); - if ($notify) { $this->notifierInterfaceMock->expects($this->once()) ->method('notify') ->with($this->orderMock, $this->invoiceMock, $this->invoiceCommentCreationMock); } - $this->invoiceMock->expects($this->once()) ->method('getEntityId') ->willReturn(2); @@ -325,14 +326,25 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase $this->invoiceCreationArgumentsMock )->willReturn($this->invoiceMock); - $this->invoiceValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->invoiceMock) - ->willReturn($errorMessages); - $this->orderValidatorMock->expects($this->once()) + $this->invoiceOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->orderMock) - ->willReturn([]); + ->with( + $this->orderMock, + $this->invoiceMock, + $capture, + $items, + $notify, + $appendComment, + $this->invoiceCommentCreationMock, + $this->invoiceCreationArgumentsMock + ) + ->willReturn($this->errorMessagesMock); + $hasMessages = true; + + $this->errorMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); + $this->errorMessagesMock->expects($this->once()) + ->method('getMessages')->willReturn($errorMessages); $this->invoiceOrder->execute( $orderId, @@ -374,16 +386,25 @@ class InvoiceOrderTest extends \PHPUnit_Framework_TestCase $this->invoiceCreationArgumentsMock )->willReturn($this->invoiceMock); - $this->invoiceValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->invoiceMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) + $this->invoiceOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $e = new \Exception(); + ->with( + $this->orderMock, + $this->invoiceMock, + $capture, + $items, + $notify, + $appendComment, + $this->invoiceCommentCreationMock, + $this->invoiceCreationArgumentsMock + ) + ->willReturn($this->errorMessagesMock); + $hasMessages = false; + $this->errorMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); + + $e = new \Exception(); $this->paymentAdapterMock->expects($this->once()) ->method('pay') ->with($this->orderMock, $this->invoiceMock, $capture) diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php index 5535ac85095c57e40420e47fa427e0205ebc6aff..f6bc3459c0cc354dd10a945613be75dcefed999d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -110,4 +110,40 @@ class OrderRepositoryTest extends \PHPUnit_Framework_TestCase $this->assertEquals($collectionMock, $this->model->getList($searchCriteriaMock)); } + + public function testSave() + { + $mapperMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order::class) + ->disableOriginalConstructor() + ->getMock(); + $orderEntity = $this->getMock(\Magento\Sales\Model\Order::class, [], [], '', false); + $extensionAttributes = $this->getMock( + \Magento\Sales\Api\Data\OrderExtension::class, + ['getShippingAssignments'], + [], + '', + false + ); + $shippingAssignment = $this->getMockBuilder(\Magento\Sales\Model\Order\ShippingAssignment::class) + ->disableOriginalConstructor() + ->setMethods(['getShipping']) + ->getMock(); + $shippingMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Shipping::class) + ->disableOriginalConstructor() + ->setMethods(['getAddress', 'getMethod']) + ->getMock(); + $orderEntity->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributes); + $orderEntity->expects($this->once())->method('getIsNotVirtual')->willReturn(true); + $extensionAttributes + ->expects($this->any()) + ->method('getShippingAssignments') + ->willReturn([$shippingAssignment]); + $shippingAssignment->expects($this->once())->method('getShipping')->willReturn($shippingMock); + $shippingMock->expects($this->once())->method('getAddress'); + $shippingMock->expects($this->once())->method('getMethod'); + $this->metadata->expects($this->once())->method('getMapper')->willReturn($mapperMock); + $mapperMock->expects($this->once())->method('save'); + $orderEntity->expects($this->any())->method('getEntityId')->willReturn(1); + $this->model->save($orderEntity); + } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php index d400c7e1c0d4ed4bd38e6ff7a4eb9a5cb460f833..d249f039a140b69e0aeebf444aff1f3961f0c0b2 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/RefundInvoiceTest.php @@ -17,15 +17,13 @@ use Magento\Sales\Api\InvoiceRepositoryInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Config as OrderConfig; -use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; -use Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface; use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; use Magento\Sales\Model\Order\CreditmemoDocumentFactory; -use Magento\Sales\Model\Order\Invoice\InvoiceValidatorInterface; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\PaymentAdapterInterface; use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; +use Magento\Sales\Model\Order\Validation\RefundInvoiceInterface; +use Magento\Sales\Model\ValidatorResultInterface; use Magento\Sales\Model\RefundInvoice; use Psr\Log\LoggerInterface; @@ -56,21 +54,6 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase */ private $creditmemoDocumentFactoryMock; - /** - * @var CreditmemoValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $creditmemoValidatorMock; - - /** - * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $orderValidatorMock; - - /** - * @var InvoiceValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $invoiceValidatorMock; - /** * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -137,9 +120,14 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase private $creditmemoItemCreationMock; /** - * @var ItemCreationValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RefundInvoiceInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $itemCreationValidatorMock; + private $refundInvoiceValidatorMock; + + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $validationMessagesMock; /** * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject @@ -160,24 +148,15 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $this->creditmemoDocumentFactoryMock = $this->getMockBuilder(CreditmemoDocumentFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->creditmemoValidatorMock = $this->getMockBuilder(CreditmemoValidatorInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->invoiceValidatorMock = $this->getMockBuilder(InvoiceValidatorInterface::class) + $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class) ->disableOriginalConstructor() ->getMock(); - - $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class) + $this->refundInvoiceValidatorMock = $this->getMockBuilder(RefundInvoiceInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->configMock = $this->getMockBuilder(OrderConfig::class) ->disableOriginalConstructor() ->getMock(); @@ -221,20 +200,17 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - - $this->itemCreationValidatorMock = $this->getMockBuilder(ItemCreationValidatorInterface::class) + $this->validationMessagesMock = $this->getMockBuilder(ValidatorResultInterface::class) ->disableOriginalConstructor() - ->getMockForAbstractClass(); + ->setMethods(['hasMessages', 'getMessages', 'addMessage']) + ->getMock(); $this->refundInvoice = new RefundInvoice( $this->resourceConnectionMock, $this->orderStateResolverMock, $this->orderRepositoryMock, $this->invoiceRepositoryMock, - $this->orderValidatorMock, - $this->invoiceValidatorMock, - $this->creditmemoValidatorMock, - $this->itemCreationValidatorMock, + $this->refundInvoiceValidatorMock, $this->creditmemoRepositoryMock, $this->paymentAdapterMock, $this->creditmemoDocumentFactoryMock, @@ -245,22 +221,27 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase } /** + * @param int $invoiceId + * @param bool $isOnline + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @throws \Magento\Sales\Exception\CouldNotRefundException + * @throws \Magento\Sales\Exception\DocumentValidationException * @dataProvider dataProvider */ - public function testOrderCreditmemo($invoiceId, $items, $notify, $appendComment) + public function testOrderCreditmemo($invoiceId, $isOnline, $items, $notify, $appendComment) { $this->resourceConnectionMock->expects($this->once()) ->method('getConnection') ->with('sales') ->willReturn($this->adapterInterface); - $this->invoiceRepositoryMock->expects($this->once()) ->method('get') ->willReturn($this->invoiceMock); $this->orderRepositoryMock->expects($this->once()) ->method('get') ->willReturn($this->orderMock); - $this->creditmemoDocumentFactoryMock->expects($this->once()) ->method('createFromInvoice') ->with( @@ -270,24 +251,23 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase ($appendComment && $notify), $this->creditmemoCreationArgumentsMock )->willReturn($this->creditmemoMock); - - $this->creditmemoValidatorMock->expects($this->once()) + $this->refundInvoiceValidatorMock->expects($this->once()) ->method('validate') - ->with($this->creditmemoMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $this->invoiceValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->invoiceMock) - ->willReturn([]); - $this->itemCreationValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoItemCreationMock) - ->willReturn([]); - $this->orderMock->expects($this->once())->method('setCustomerNoteNotify')->with($notify); + ->with( + $this->invoiceMock, + $this->orderMock, + $this->creditmemoMock, + $items, + $isOnline, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = false; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); $this->paymentAdapterMock->expects($this->once()) ->method('refund') ->with($this->creditmemoMock, $this->orderMock) @@ -315,7 +295,6 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase ->method('setState') ->with(\Magento\Sales\Model\Order\Creditmemo::STATE_REFUNDED) ->willReturnSelf(); - $this->creditmemoRepositoryMock->expects($this->once()) ->method('save') ->with($this->creditmemoMock) @@ -338,7 +317,7 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $this->refundInvoice->execute( $invoiceId, $items, - false, + true, $notify, $appendComment, $this->creditmemoCommentCreationMock, @@ -356,6 +335,7 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $items = [1 => $this->creditmemoItemCreationMock]; $notify = true; $appendComment = true; + $isOnline = false; $errorMessages = ['error1', 'error2']; $this->invoiceRepositoryMock->expects($this->once()) @@ -375,22 +355,25 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $this->creditmemoCreationArgumentsMock )->willReturn($this->creditmemoMock); - $this->creditmemoValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoMock) - ->willReturn($errorMessages); - $this->orderValidatorMock->expects($this->once()) + $this->refundInvoiceValidatorMock->expects($this->once()) ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $this->invoiceValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->invoiceMock) - ->willReturn([]); - $this->itemCreationValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoItemCreationMock) - ->willReturn([]); + ->with( + $this->invoiceMock, + $this->orderMock, + $this->creditmemoMock, + $items, + $isOnline, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = true; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); + $this->validationMessagesMock->expects($this->once()) + ->method('getMessages')->willReturn($errorMessages); $this->assertEquals( $errorMessages, @@ -415,6 +398,7 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $items = [1 => $this->creditmemoItemCreationMock]; $notify = true; $appendComment = true; + $isOnline = false; $this->resourceConnectionMock->expects($this->once()) ->method('getConnection') ->with('sales') @@ -437,22 +421,23 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase $this->creditmemoCreationArgumentsMock )->willReturn($this->creditmemoMock); - $this->creditmemoValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $this->invoiceValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->invoiceMock) - ->willReturn([]); - $this->itemCreationValidatorMock->expects($this->once()) + $this->refundInvoiceValidatorMock->expects($this->once()) ->method('validate') - ->with($this->creditmemoItemCreationMock) - ->willReturn([]); + ->with( + $this->invoiceMock, + $this->orderMock, + $this->creditmemoMock, + $items, + $isOnline, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = false; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); $e = new \Exception(); $this->paymentAdapterMock->expects($this->once()) @@ -485,8 +470,8 @@ class RefundInvoiceTest extends \PHPUnit_Framework_TestCase ->getMockForAbstractClass(); return [ - 'TestWithNotifyTrue' => [1, [1 => $creditmemoItemCreationMock], true, true], - 'TestWithNotifyFalse' => [1, [1 => $creditmemoItemCreationMock], false, true], + 'TestWithNotifyTrue' => [1, true, [1 => $creditmemoItemCreationMock], true, true], + 'TestWithNotifyFalse' => [1, true, [1 => $creditmemoItemCreationMock], false, true], ]; } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php index 7cc2673981c03d9fe858b38f10f7de16cd64287a..4b61453e3c6a2a9cc996dce60a52714fe0113cc2 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/RefundOrderTest.php @@ -15,14 +15,13 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Config as OrderConfig; -use Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface; -use Magento\Sales\Model\Order\Creditmemo\Item\Validation\CreationQuantityValidator; use Magento\Sales\Model\Order\CreditmemoDocumentFactory; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; +use Magento\Sales\Model\Order\Validation\RefundOrderInterface; use Magento\Sales\Model\Order\PaymentAdapterInterface; use Magento\Sales\Model\Order\Creditmemo\NotifierInterface; use Magento\Sales\Model\RefundOrder; +use Magento\Sales\Model\ValidatorResultInterface; use Psr\Log\LoggerInterface; use Magento\Sales\Api\Data\CreditmemoItemCreationInterface; @@ -48,16 +47,6 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase */ private $creditmemoDocumentFactoryMock; - /** - * @var CreditmemoValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $creditmemoValidatorMock; - - /** - * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $orderValidatorMock; - /** * @var PaymentAdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -119,15 +108,20 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase private $loggerMock; /** - * @var Order\Creditmemo\ItemCreationValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RefundOrderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $itemCreationValidatorMock; + private $refundOrderValidatorMock; /** * @var CreditmemoItemCreationInterface|\PHPUnit_Framework_MockObject_MockObject */ private $creditmemoItemCreationMock; + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $validationMessagesMock; + protected function setUp() { $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) @@ -139,10 +133,7 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase $this->creditmemoDocumentFactoryMock = $this->getMockBuilder(CreditmemoDocumentFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->creditmemoValidatorMock = $this->getMockBuilder(CreditmemoValidatorInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class) + $this->refundOrderValidatorMock = $this->getMockBuilder(RefundOrderInterface::class) ->disableOriginalConstructor() ->getMock(); $this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class) @@ -178,23 +169,22 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase $this->adapterInterface = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->itemCreationValidatorMock = $this->getMockBuilder(Order\Creditmemo\ItemCreationValidatorInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); $this->creditmemoItemCreationMock = $this->getMockBuilder(CreditmemoItemCreationInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->validationMessagesMock = $this->getMockBuilder(ValidatorResultInterface::class) + ->disableOriginalConstructor() + ->setMethods(['hasMessages', 'getMessages', 'addMessage']) + ->getMock(); $this->refundOrder = new RefundOrder( $this->resourceConnectionMock, $this->orderStateResolverMock, $this->orderRepositoryMock, - $this->orderValidatorMock, - $this->creditmemoValidatorMock, - $this->itemCreationValidatorMock, $this->creditmemoRepositoryMock, $this->paymentAdapterMock, $this->creditmemoDocumentFactoryMock, + $this->refundOrderValidatorMock, $this->notifierMock, $this->configMock, $this->loggerMock @@ -202,6 +192,11 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase } /** + * @param int $orderId + * @param bool $notify + * @param bool $appendComment + * @throws \Magento\Sales\Exception\CouldNotRefundException + * @throws \Magento\Sales\Exception\DocumentValidationException * @dataProvider dataProvider */ public function testOrderCreditmemo($orderId, $notify, $appendComment) @@ -223,22 +218,21 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase ($appendComment && $notify), $this->creditmemoCreationArgumentsMock )->willReturn($this->creditmemoMock); - $this->creditmemoValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $this->itemCreationValidatorMock->expects($this->once()) + $this->refundOrderValidatorMock->expects($this->once()) ->method('validate') ->with( - reset($items), - [CreationQuantityValidator::class], - $this->orderMock - )->willReturn([]); - $this->orderMock->expects($this->once())->method('setCustomerNoteNotify')->with($notify); + $this->orderMock, + $this->creditmemoMock, + $items, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = false; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); $this->paymentAdapterMock->expects($this->once()) ->method('refund') ->with($this->creditmemoMock, $this->orderMock) @@ -279,7 +273,6 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase ->method('notify') ->with($this->orderMock, $this->creditmemoMock, $this->creditmemoCommentCreationMock); } - $this->creditmemoMock->expects($this->once()) ->method('getEntityId') ->willReturn(2); @@ -322,18 +315,23 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase $this->creditmemoCreationArgumentsMock )->willReturn($this->creditmemoMock); - $this->creditmemoValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoMock) - ->willReturn($errorMessages); - $this->orderValidatorMock->expects($this->once()) + $this->refundOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $this->itemCreationValidatorMock->expects($this->once()) - ->method('validate') - ->with(reset($items), [CreationQuantityValidator::class], $this->orderMock) - ->willReturn([]); + ->with( + $this->orderMock, + $this->creditmemoMock, + $items, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = true; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); + $this->validationMessagesMock->expects($this->once()) + ->method('getMessages')->willReturn($errorMessages); $this->assertEquals( $errorMessages, @@ -373,18 +371,21 @@ class RefundOrderTest extends \PHPUnit_Framework_TestCase ($appendComment && $notify), $this->creditmemoCreationArgumentsMock )->willReturn($this->creditmemoMock); - $this->itemCreationValidatorMock->expects($this->once()) + $this->refundOrderValidatorMock->expects($this->once()) ->method('validate') - ->with(reset($items), [CreationQuantityValidator::class], $this->orderMock) - ->willReturn([]); - $this->creditmemoValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->creditmemoMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); + ->with( + $this->orderMock, + $this->creditmemoMock, + $items, + $notify, + $appendComment, + $this->creditmemoCommentCreationMock, + $this->creditmemoCreationArgumentsMock + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = false; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); $e = new \Exception(); $this->paymentAdapterMock->expects($this->once()) ->method('refund') diff --git a/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php index b719babf209f0ceb98c4d019f1e5929b23e7e11f..1daf7a64263b817654bf73ee2155506ea9a5f42d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php @@ -18,11 +18,11 @@ use Magento\Sales\Api\ShipmentRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Config as OrderConfig; use Magento\Sales\Model\Order\OrderStateResolverInterface; -use Magento\Sales\Model\Order\OrderValidatorInterface; use Magento\Sales\Model\Order\ShipmentDocumentFactory; use Magento\Sales\Model\Order\Shipment\NotifierInterface; use Magento\Sales\Model\Order\Shipment\OrderRegistrarInterface; -use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface; +use Magento\Sales\Model\Order\Validation\ShipOrderInterface; +use Magento\Sales\Model\ValidatorResultInterface; use Magento\Sales\Model\ShipOrder; use Psr\Log\LoggerInterface; @@ -49,14 +49,9 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase private $shipmentDocumentFactoryMock; /** - * @var ShipmentValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ShipOrderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $shipmentValidatorMock; - - /** - * @var OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $orderValidatorMock; + private $shipOrderValidatorMock; /** * @var OrderRegistrarInterface|\PHPUnit_Framework_MockObject_MockObject @@ -109,20 +104,25 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase private $shipmentMock; /** - * @var AdapterInterface + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ private $adapterMock; /** - * @var ShipmentTrackCreationInterface + * @var ShipmentTrackCreationInterface|\PHPUnit_Framework_MockObject_MockObject */ private $trackMock; /** - * @var ShipmentPackageInterface + * @var ShipmentPackageInterface|\PHPUnit_Framework_MockObject_MockObject */ private $packageMock; + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $validationMessagesMock; + /** * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -133,75 +133,58 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->shipmentDocumentFactoryMock = $this->getMockBuilder(ShipmentDocumentFactory::class) ->disableOriginalConstructor() ->getMock(); - - $this->shipmentValidatorMock = $this->getMockBuilder(ShipmentValidatorInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->orderRegistrarMock = $this->getMockBuilder(OrderRegistrarInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->configMock = $this->getMockBuilder(OrderConfig::class) ->disableOriginalConstructor() ->getMock(); - $this->shipmentRepositoryMock = $this->getMockBuilder(ShipmentRepositoryInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->notifierInterfaceMock = $this->getMockBuilder(NotifierInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->shipmentCommentCreationMock = $this->getMockBuilder(ShipmentCommentCreationInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->shipmentCreationArgumentsMock = $this->getMockBuilder(ShipmentCreationArgumentsInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->orderMock = $this->getMockBuilder(OrderInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->shipmentMock = $this->getMockBuilder(ShipmentInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->packageMock = $this->getMockBuilder(ShipmentPackageInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->trackMock = $this->getMockBuilder(ShipmentTrackCreationInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->adapterMock = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - + $this->shipOrderValidatorMock = $this->getMockBuilder(ShipOrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->validationMessagesMock = $this->getMockBuilder(ValidatorResultInterface::class) + ->disableOriginalConstructor() + ->setMethods(['hasMessages', 'getMessages', 'addMessage']) + ->getMock(); $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $helper->getObject( @@ -209,20 +192,25 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase [ 'resourceConnection' => $this->resourceConnectionMock, 'orderRepository' => $this->orderRepositoryMock, - 'shipmentRepository' => $this->shipmentRepositoryMock, 'shipmentDocumentFactory' => $this->shipmentDocumentFactoryMock, - 'shipmentValidator' => $this->shipmentValidatorMock, - 'orderValidator' => $this->orderValidatorMock, 'orderStateResolver' => $this->orderStateResolverMock, - 'orderRegistrar' => $this->orderRegistrarMock, - 'notifierInterface' => $this->notifierInterfaceMock, 'config' => $this->configMock, - 'logger' => $this->loggerMock + 'shipmentRepository' => $this->shipmentRepositoryMock, + 'shipOrderValidator' => $this->shipOrderValidatorMock, + 'notifierInterface' => $this->notifierInterfaceMock, + 'logger' => $this->loggerMock, + 'orderRegistrar' => $this->orderRegistrarMock ] ); } /** + * @param int $orderId + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @throws \Magento\Sales\Exception\CouldNotShipException + * @throws \Magento\Sales\Exception\DocumentValidationException * @dataProvider dataProvider */ public function testExecute($orderId, $items, $notify, $appendComment) @@ -231,11 +219,9 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase ->method('getConnection') ->with('sales') ->willReturn($this->adapterMock); - $this->orderRepositoryMock->expects($this->once()) ->method('get') ->willReturn($this->orderMock); - $this->shipmentDocumentFactoryMock->expects($this->once()) ->method('create') ->with( @@ -247,65 +233,61 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase [$this->packageMock], $this->shipmentCreationArgumentsMock )->willReturn($this->shipmentMock); - - $this->shipmentValidatorMock->expects($this->once()) + $this->shipOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->shipmentMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - + ->with( + $this->orderMock, + $this->shipmentMock, + $items, + $notify, + $appendComment, + $this->shipmentCommentCreationMock, + [$this->trackMock], + [$this->packageMock] + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = false; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); $this->orderRegistrarMock->expects($this->once()) ->method('register') ->with($this->orderMock, $this->shipmentMock) ->willReturn($this->orderMock); - $this->orderStateResolverMock->expects($this->once()) ->method('getStateForOrder') ->with($this->orderMock, [OrderStateResolverInterface::IN_PROGRESS]) ->willReturn(Order::STATE_PROCESSING); - $this->orderMock->expects($this->once()) ->method('setState') ->with(Order::STATE_PROCESSING) ->willReturnSelf(); - $this->orderMock->expects($this->once()) ->method('getState') ->willReturn(Order::STATE_PROCESSING); - $this->configMock->expects($this->once()) ->method('getStateDefaultStatus') ->with(Order::STATE_PROCESSING) ->willReturn('Processing'); - $this->orderMock->expects($this->once()) ->method('setStatus') ->with('Processing') ->willReturnSelf(); - $this->shipmentRepositoryMock->expects($this->once()) ->method('save') ->with($this->shipmentMock) ->willReturn($this->shipmentMock); - $this->orderRepositoryMock->expects($this->once()) ->method('save') ->with($this->orderMock) ->willReturn($this->orderMock); - if ($notify) { $this->notifierInterfaceMock->expects($this->once()) ->method('notify') ->with($this->orderMock, $this->shipmentMock, $this->shipmentCommentCreationMock); } - $this->shipmentMock->expects($this->once()) ->method('getEntityId') ->willReturn(2); - $this->assertEquals( 2, $this->model->execute( @@ -348,14 +330,24 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase $this->shipmentCreationArgumentsMock )->willReturn($this->shipmentMock); - $this->shipmentValidatorMock->expects($this->once()) + $this->shipOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->shipmentMock) - ->willReturn($errorMessages); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); + ->with( + $this->orderMock, + $this->shipmentMock, + $items, + $notify, + $appendComment, + $this->shipmentCommentCreationMock, + [$this->trackMock], + [$this->packageMock] + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = true; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); + $this->validationMessagesMock->expects($this->once()) + ->method('getMessages')->willReturn($errorMessages); $this->model->execute( $orderId, @@ -372,9 +364,12 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Magento\Sales\Api\Exception\CouldNotShipExceptionInterface */ - public function testCouldNotInvoiceException() + public function testCouldNotShipException() { $orderId = 1; + $items = [1 => 2]; + $notify = true; + $appendComment = true; $this->resourceConnectionMock->expects($this->once()) ->method('getConnection') ->with('sales') @@ -387,33 +382,53 @@ class ShipOrderTest extends \PHPUnit_Framework_TestCase $this->shipmentDocumentFactoryMock->expects($this->once()) ->method('create') ->with( - $this->orderMock + $this->orderMock, + $items, + [$this->trackMock], + $this->shipmentCommentCreationMock, + ($appendComment && $notify), + [$this->packageMock], + $this->shipmentCreationArgumentsMock )->willReturn($this->shipmentMock); - - $this->shipmentValidatorMock->expects($this->once()) + $this->shipOrderValidatorMock->expects($this->once()) ->method('validate') - ->with($this->shipmentMock) - ->willReturn([]); - $this->orderValidatorMock->expects($this->once()) - ->method('validate') - ->with($this->orderMock) - ->willReturn([]); - $e = new \Exception(); + ->with( + $this->orderMock, + $this->shipmentMock, + $items, + $notify, + $appendComment, + $this->shipmentCommentCreationMock, + [$this->trackMock], + [$this->packageMock] + ) + ->willReturn($this->validationMessagesMock); + $hasMessages = false; + $this->validationMessagesMock->expects($this->once()) + ->method('hasMessages')->willReturn($hasMessages); + $exception = new \Exception(); $this->orderRegistrarMock->expects($this->once()) ->method('register') ->with($this->orderMock, $this->shipmentMock) - ->willThrowException($e); + ->willThrowException($exception); $this->loggerMock->expects($this->once()) ->method('critical') - ->with($e); + ->with($exception); $this->adapterMock->expects($this->once()) ->method('rollBack'); $this->model->execute( - $orderId + $orderId, + $items, + $notify, + $appendComment, + $this->shipmentCommentCreationMock, + [$this->trackMock], + [$this->packageMock], + $this->shipmentCreationArgumentsMock ); } diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index 4b921587c292b54bdd49a40cb174a7de373917f5..4e2487d9dcada2eaf224f8cc30f65fc8fa4a4fc8 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -105,6 +105,11 @@ <preference for="Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface" type="Magento\Sales\Model\Order\Shipment\ShipmentValidator"/> <preference for="Magento\Sales\Model\Order\Creditmemo\CreditmemoValidatorInterface" type="Magento\Sales\Model\Order\Creditmemo\CreditmemoValidator"/> <preference for="Magento\Sales\Model\Order\Creditmemo\ItemCreationValidatorInterface" type="Magento\Sales\Model\Order\Creditmemo\ItemCreationValidator"/> + <preference for="Magento\Sales\Model\ValidatorResultInterface" type="Magento\Sales\Model\ValidatorResult"/> + <preference for="Magento\Sales\Model\Order\Validation\InvoiceOrderInterface" type="Magento\Sales\Model\Order\Validation\InvoiceOrder"/> + <preference for="Magento\Sales\Model\Order\Validation\RefundInvoiceInterface" type="Magento\Sales\Model\Order\Validation\RefundInvoice"/> + <preference for="Magento\Sales\Model\Order\Validation\RefundOrderInterface" type="Magento\Sales\Model\Order\Validation\RefundOrder"/> + <preference for="Magento\Sales\Model\Order\Validation\ShipOrderInterface" type="Magento\Sales\Model\Order\Validation\ShipOrder"/> <preference for="Magento\Sales\Model\Order\Creditmemo\NotifierInterface" type="Magento\Sales\Model\Order\Creditmemo\Notifier"/> <preference for="Magento\Sales\Api\RefundOrderInterface" type="Magento\Sales\Model\RefundOrder"/> <preference for="Magento\Sales\Api\RefundInvoiceInterface" type="Magento\Sales\Model\RefundInvoice"/> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml index 4b53e0ccb1e9fefd91ea23c555dc64c7f617ca72..fe212490a2fd00c9e18fc6fea8ab28b01e34e45d 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml @@ -8,7 +8,7 @@ /** @var \Magento\Sales\Block\Adminhtml\Order\Create\Form $block */ ?> -<form id="edit_form" data-order-config='<?php /* @escapeNotVerified */ echo $block->getOrderDataJson() ?>' data-load-base-url="<?php /* @escapeNotVerified */ echo $block->getLoadBlockUrl() ?>" action="<?php /* @escapeNotVerified */ echo $block->getSaveUrl() ?>" method="post" enctype="multipart/form-data"> +<form id="edit_form" data-order-config='<?php echo $block->escapeHtml($block->getOrderDataJson()) ?>' data-load-base-url="<?php /* @escapeNotVerified */ echo $block->getLoadBlockUrl() ?>" action="<?php /* @escapeNotVerified */ echo $block->getSaveUrl() ?>" method="post" enctype="multipart/form-data"> <?php echo $block->getBlockHtml('formkey')?> <div id="order-message"> <?php echo $block->getChildHtml('message') ?> diff --git a/app/code/Magento/SalesInventory/LICENSE.txt b/app/code/Magento/SalesInventory/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/SalesInventory/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/SalesInventory/LICENSE_AFL.txt b/app/code/Magento/SalesInventory/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/SalesInventory/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/SalesInventory/Model/Order/ReturnProcessor.php b/app/code/Magento/SalesInventory/Model/Order/ReturnProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..7752d7131031cf0daa63f4c8b8bf49609e3090ed --- /dev/null +++ b/app/code/Magento/SalesInventory/Model/Order/ReturnProcessor.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Model\Order; + +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\CreditmemoItemInterface; +use Magento\Sales\Api\Data\OrderInterface; + +/** + * Class ReturnProcessor + */ +class ReturnProcessor +{ + /** + * @var \Magento\CatalogInventory\Api\StockManagementInterface + */ + private $stockManagement; + + /** + * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor + */ + private $stockIndexerProcessor; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor + */ + private $priceIndexer; + + /** + * @var \Magento\Sales\Api\CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var \Magento\Sales\Api\OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var \Magento\Sales\Api\OrderItemRepositoryInterface + */ + private $orderItemRepository; + + /** + * ReturnToStockPlugin constructor. + * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration + * @param \Magento\CatalogInventory\Api\StockManagementInterface $stockManagement + * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexer + * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer + * @param \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository + * @param \Magento\Sales\Api\OrderItemRepositoryInterface $orderItemRepository + */ + public function __construct( + \Magento\CatalogInventory\Api\StockManagementInterface $stockManagement, + \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexer, + \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer, + \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, + \Magento\Sales\Api\OrderItemRepositoryInterface $orderItemRepository + ) { + $this->stockManagement = $stockManagement; + $this->stockIndexerProcessor = $stockIndexer; + $this->priceIndexer = $priceIndexer; + $this->creditmemoRepository = $creditmemoRepository; + $this->storeManager = $storeManager; + $this->orderRepository = $orderRepository; + $this->orderItemRepository = $orderItemRepository; + } + + /** + * @param CreditmemoInterface $creditmemo + * @param OrderInterface $order + * @param array $returnToStockItems + * @return void + */ + public function execute( + CreditmemoInterface $creditmemo, + OrderInterface $order, + array $returnToStockItems = [] + ) { + $itemsToUpdate = []; + foreach ($creditmemo->getItems() as $item) { + $qty = $item->getQty(); + $productId = $item->getProductId(); + $orderItem = $this->orderItemRepository->get($item->getOrderItemId()); + $parentItemId = $orderItem->getParentItemId(); + if ($this->canReturnItem($item, $qty, $parentItemId, $returnToStockItems)) { + $parentItem = $parentItemId ? $this->getItemByOrderId($creditmemo, $parentItemId) : false; + $qty = $parentItem ? $parentItem->getQty() * $qty : $qty; + if (isset($itemsToUpdate[$productId])) { + $itemsToUpdate[$productId] += $qty; + } else { + $itemsToUpdate[$productId] = $qty; + } + } + } + + if (!empty($itemsToUpdate)) { + $store = $this->storeManager->getStore($order->getStoreId()); + foreach ($itemsToUpdate as $productId => $qty) { + $this->stockManagement->backItemQty( + $productId, + $qty, + $store->getWebsiteId() + ); + } + + $updatedItemIds = array_keys($itemsToUpdate); + $this->stockIndexerProcessor->reindexList($updatedItemIds); + $this->priceIndexer->reindexList($updatedItemIds); + } + } + + /** + * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo + * @param int $parentItemId + * @return bool|CreditmemoItemInterface + */ + private function getItemByOrderId(\Magento\Sales\Api\Data\CreditmemoInterface $creditmemo, $parentItemId) + { + foreach ($creditmemo->getItems() as $item) { + if ($item->getOrderItemId() == $parentItemId) { + return $item; + } + } + return false; + } + + /** + * @param \Magento\Sales\Api\Data\CreditmemoItemInterface $item + * @param int $qty + * @param int[] $returnToStockItems + * @param int $parentItemId + * @return bool + */ + private function canReturnItem( + \Magento\Sales\Api\Data\CreditmemoItemInterface $item, + $qty, + $parentItemId = null, + array $returnToStockItems = [] + ) { + return (in_array($item->getOrderItemId(), $returnToStockItems) || in_array($parentItemId, $returnToStockItems)) + && $qty; + } +} diff --git a/app/code/Magento/SalesInventory/Model/Order/ReturnValidator.php b/app/code/Magento/SalesInventory/Model/Order/ReturnValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..2195883334fcf6dfa21a55a49797f64a4ec0faa4 --- /dev/null +++ b/app/code/Magento/SalesInventory/Model/Order/ReturnValidator.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Model\Order; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\CreditmemoItemInterface; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\OrderItemRepositoryInterface; + +/** + * Class ReturnProcessor + */ +class ReturnValidator +{ + /** + * @var OrderItemRepositoryInterface + */ + private $orderItemRepository; + + /** + * ReturnValidator constructor. + * @param OrderItemRepositoryInterface $orderItemRepository + */ + public function __construct(OrderItemRepositoryInterface $orderItemRepository) + { + $this->orderItemRepository = $orderItemRepository; + } + + /** + * @param int[] $returnToStockItems + * @param CreditmemoInterface $creditmemo + * @return \Magento\Framework\Phrase|null + */ + public function validate($returnToStockItems, CreditmemoInterface $creditmemo) + { + $creditmemoItems = $creditmemo->getItems(); + + /** @var int $item */ + foreach ($returnToStockItems as $item) { + try { + $orderItem = $this->orderItemRepository->get($item); + if (!$this->isOrderItemPartOfCreditmemo($creditmemoItems, $orderItem)) { + return __('The "%1" product is not part of the current creditmemo.', $orderItem->getSku()); + } + } catch (NoSuchEntityException $e) { + return __('The return to stock argument contains product item that is not part of the original order.'); + } + } + return null; + } + + /** + * @param CreditmemoItemInterface[] $creditmemoItems + * @param OrderItemInterface $orderItem + * @return bool + */ + private function isOrderItemPartOfCreditmemo(array $creditmemoItems, OrderItemInterface $orderItem) + { + foreach ($creditmemoItems as $creditmemoItem) { + if ($creditmemoItem->getOrderItemId() == $orderItem->getItemId()) { + return true; + } + } + return false; + } +} diff --git a/app/code/Magento/SalesInventory/Model/Plugin/Order/ReturnToStockInvoice.php b/app/code/Magento/SalesInventory/Model/Plugin/Order/ReturnToStockInvoice.php new file mode 100644 index 0000000000000000000000000000000000000000..53a393d83353b4922f61641160dcf3c2aa8b6d85 --- /dev/null +++ b/app/code/Magento/SalesInventory/Model/Plugin/Order/ReturnToStockInvoice.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Model\Plugin\Order; + +/** + * Class ReturnToStockInvoice + */ +class ReturnToStockInvoice +{ + /** + * @var \Magento\SalesInventory\Model\Order\ReturnProcessor + */ + private $returnProcessor; + + /** + * @var \Magento\Sales\Api\CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + /** + * @var \Magento\Sales\Api\OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var \Magento\Sales\Api\InvoiceRepositoryInterface + */ + private $invoiceRepository; + + /** + * @var \Magento\CatalogInventory\Api\StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * ReturnToStockInvoice constructor. + * @param \Magento\SalesInventory\Model\Order\ReturnProcessor $returnProcessor + * @param \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository + * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository + * @param \Magento\Sales\Api\InvoiceRepositoryInterface $invoiceRepository + * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration + */ + public function __construct( + \Magento\SalesInventory\Model\Order\ReturnProcessor $returnProcessor, + \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository, + \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, + \Magento\Sales\Api\InvoiceRepositoryInterface $invoiceRepository, + \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration + ) { + $this->returnProcessor = $returnProcessor; + $this->creditmemoRepository = $creditmemoRepository; + $this->orderRepository = $orderRepository; + $this->invoiceRepository = $invoiceRepository; + $this->stockConfiguration = $stockConfiguration; + } + + /** + * @param \Magento\Sales\Api\RefundInvoiceInterface $refundService + * @param int $resultEntityId + * @param int $invoiceId + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param bool|null $isOnline + * @param bool|null $notify + * @param bool|null $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return int + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute( + \Magento\Sales\Api\RefundInvoiceInterface $refundService, + $resultEntityId, + $invoiceId, + array $items = [], + $isOnline = false, + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + if ($this->stockConfiguration->isAutoReturnEnabled()) { + return $resultEntityId; + } + + $invoice = $this->invoiceRepository->get($invoiceId); + $order = $this->orderRepository->get($invoice->getOrderId()); + + $returnToStockItems = []; + if ($arguments !== null + && $arguments->getExtensionAttributes() !== null + && $arguments->getExtensionAttributes()->getReturnToStockItems() !== null + ) { + $returnToStockItems = $arguments->getExtensionAttributes()->getReturnToStockItems(); + } + + $creditmemo = $this->creditmemoRepository->get($resultEntityId); + $this->returnProcessor->execute($creditmemo, $order, $returnToStockItems); + + return $resultEntityId; + } +} diff --git a/app/code/Magento/SalesInventory/Model/Plugin/Order/ReturnToStockOrder.php b/app/code/Magento/SalesInventory/Model/Plugin/Order/ReturnToStockOrder.php new file mode 100644 index 0000000000000000000000000000000000000000..b21f9f6b1050636738a67c88151ceb1780f3b820 --- /dev/null +++ b/app/code/Magento/SalesInventory/Model/Plugin/Order/ReturnToStockOrder.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Model\Plugin\Order; + +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\SalesInventory\Model\Order\ReturnProcessor; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\RefundOrderInterface; + +/** + * Class ReturnToStock + */ +class ReturnToStockOrder +{ + /** + * @var ReturnProcessor + */ + private $returnProcessor; + + /** + * @var CreditmemoRepositoryInterface + */ + private $creditmemoRepository; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * ReturnToStockPlugin constructor. + * + * @param ReturnProcessor $returnProcessor + * @param CreditmemoRepositoryInterface $creditmemoRepository + * @param OrderRepositoryInterface $orderRepository + * @param StockConfigurationInterface $stockConfiguration + */ + public function __construct( + ReturnProcessor $returnProcessor, + CreditmemoRepositoryInterface $creditmemoRepository, + OrderRepositoryInterface $orderRepository, + StockConfigurationInterface $stockConfiguration + ) { + $this->returnProcessor = $returnProcessor; + $this->creditmemoRepository = $creditmemoRepository; + $this->orderRepository = $orderRepository; + $this->stockConfiguration = $stockConfiguration; + } + + /** + * @param RefundOrderInterface $refundService + * @param int $resultEntityId + * @param int $orderId + * @param \Magento\Sales\Api\Data\CreditmemoItemCreationInterface[] $items + * @param bool|null $notify + * @param bool|null $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return int + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute( + RefundOrderInterface $refundService, + $resultEntityId, + $orderId, + array $items = [], + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + if ($this->stockConfiguration->isAutoReturnEnabled()) { + return $resultEntityId; + } + + $order = $this->orderRepository->get($orderId); + + $returnToStockItems = []; + if ($arguments !== null + && $arguments->getExtensionAttributes() !== null + && $arguments->getExtensionAttributes()->getReturnToStockItems() !== null + ) { + $returnToStockItems = $arguments->getExtensionAttributes()->getReturnToStockItems(); + } + + $creditmemo = $this->creditmemoRepository->get($resultEntityId); + $this->returnProcessor->execute($creditmemo, $order, $returnToStockItems); + + return $resultEntityId; + } +} diff --git a/app/code/Magento/SalesInventory/Model/Plugin/Order/Validation/InvoiceRefundCreationArguments.php b/app/code/Magento/SalesInventory/Model/Plugin/Order/Validation/InvoiceRefundCreationArguments.php new file mode 100644 index 0000000000000000000000000000000000000000..7dfa2893c588f713438ee630e8f7fcc7a670bb32 --- /dev/null +++ b/app/code/Magento/SalesInventory/Model/Plugin/Order/Validation/InvoiceRefundCreationArguments.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Model\Plugin\Order\Validation; + +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Model\Order\Validation\RefundInvoiceInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\SalesInventory\Model\Order\ReturnValidator; +use Magento\Sales\Model\ValidatorResultInterface; + +/** + * Class CreditmemoCreationArguments + */ +class InvoiceRefundCreationArguments +{ + /** + * @var ReturnValidator + */ + private $returnValidator; + + /** + * InvoiceRefundCreationArguments constructor. + * @param ReturnValidator $returnValidator + */ + public function __construct( + ReturnValidator $returnValidator + ) { + $this->returnValidator = $returnValidator; + } + + /** + * @param RefundInvoiceInterface $refundInvoiceValidator + * @param ValidatorResultInterface $validationResults + * @param InvoiceInterface $invoice + * @param OrderInterface $order + * @param CreditmemoInterface $creditmemo + * @param array $items + * @param bool $isOnline + * @param bool $notify + * @param bool $appendComment + * @param \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface|null $comment + * @param \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface|null $arguments + * @return ValidatorResultInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function afterValidate( + RefundInvoiceInterface $refundInvoiceValidator, + ValidatorResultInterface $validationResults, + InvoiceInterface $invoice, + OrderInterface $order, + CreditmemoInterface $creditmemo, + array $items = [], + $isOnline = false, + $notify = false, + $appendComment = false, + \Magento\Sales\Api\Data\CreditmemoCommentCreationInterface $comment = null, + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface $arguments = null + ) { + if ($this->isReturnToStockItems($arguments)) { + return $validationResults; + } + + /** @var int[] $returnToStockItems */ + $returnToStockItems = $arguments->getExtensionAttributes()->getReturnToStockItems(); + $validationMessage = $this->returnValidator->validate($returnToStockItems, $creditmemo); + if ($validationMessage) { + $validationResults->addMessage($validationMessage); + } + + return $validationResults; + } + + /** + * @param CreditmemoCreationArgumentsInterface|null $arguments + * @return bool + */ + private function isReturnToStockItems($arguments) + { + return $arguments === null + || $arguments->getExtensionAttributes() === null + || $arguments->getExtensionAttributes()->getReturnToStockItems() === null; + } +} diff --git a/app/code/Magento/SalesInventory/Model/Plugin/Order/Validation/OrderRefundCreationArguments.php b/app/code/Magento/SalesInventory/Model/Plugin/Order/Validation/OrderRefundCreationArguments.php new file mode 100644 index 0000000000000000000000000000000000000000..29411c7c5e8c6b072203267660f8eafef1306ab7 --- /dev/null +++ b/app/code/Magento/SalesInventory/Model/Plugin/Order/Validation/OrderRefundCreationArguments.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Model\Plugin\Order\Validation; + +use Magento\Sales\Api\Data\CreditmemoCommentCreationInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order\Validation\RefundOrderInterface; +use Magento\SalesInventory\Model\Order\ReturnValidator; +use Magento\Sales\Model\ValidatorResultInterface; + +/** + * Class OrderRefundCreationArguments + */ +class OrderRefundCreationArguments +{ + /** + * @var ReturnValidator + */ + private $returnValidator; + + /** + * OrderRefundCreationArguments constructor. + * @param ReturnValidator $returnValidator + */ + public function __construct( + ReturnValidator $returnValidator + ) { + $this->returnValidator = $returnValidator; + } + + /** + * @param RefundOrderInterface $refundOrderValidator + * @param ValidatorResultInterface $validationResults + * @param OrderInterface $order + * @param CreditmemoInterface $creditmemo + * @param array $items + * @param bool $notify + * @param bool $appendComment + * @param CreditmemoCommentCreationInterface|null $comment + * @param CreditmemoCreationArgumentsInterface|null $arguments + * @return ValidatorResultInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterValidate( + RefundOrderInterface $refundOrderValidator, + ValidatorResultInterface $validationResults, + OrderInterface $order, + CreditmemoInterface $creditmemo, + array $items = [], + $notify = false, + $appendComment = false, + CreditmemoCommentCreationInterface $comment = null, + CreditmemoCreationArgumentsInterface $arguments = null + ) { + if ($this->isReturnToStockItems($arguments)) { + return $validationResults; + } + + $returnToStockItems = $arguments->getExtensionAttributes()->getReturnToStockItems(); + $validationMessage = $this->returnValidator->validate($returnToStockItems, $creditmemo); + if ($validationMessage) { + $validationResults->addMessage($validationMessage); + } + + return $validationResults; + } + + /** + * @param CreditmemoCreationArgumentsInterface|null $arguments + * @return bool + */ + private function isReturnToStockItems($arguments) + { + return $arguments === null + || $arguments->getExtensionAttributes() === null + || $arguments->getExtensionAttributes()->getReturnToStockItems() === null; + } +} diff --git a/app/code/Magento/SalesInventory/README.md b/app/code/Magento/SalesInventory/README.md new file mode 100644 index 0000000000000000000000000000000000000000..915569ddd3ce24a240f5938c4e38e5275e9ada2c --- /dev/null +++ b/app/code/Magento/SalesInventory/README.md @@ -0,0 +1 @@ +Magento_SalesInventory module allows retrieve and update stock attributes related to Magento_Sales, such as status and quantity. diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..523759d54645a1e2db471db948fa116cf59cd32e --- /dev/null +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnProcessorTest.php @@ -0,0 +1,195 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Test\Unit\Model\Order; + +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Api\StockManagementInterface; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\CreditmemoItemInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\OrderItemRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\SalesInventory\Model\Order\ReturnProcessor; + +/** + * Class ReturnProcessorTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ReturnProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderInterface + */ + private $orderMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoInterface + */ + private $creditmemoMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|StockManagementInterface + */ + private $stockManagementMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\CatalogInventory\Model\Indexer\Stock\Processor + */ + private $stockIndexerProcessorMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Indexer\Product\Price\Processor + */ + private $priceIndexerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoRepositoryInterface + */ + private $creditmemoRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|StoreManagerInterface + */ + private $storeManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderRepositoryInterface + */ + private $orderRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderItemRepositoryInterface + */ + private $orderItemRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoItemInterface + */ + private $creditmemoItemMock; + + /** @var ReturnProcessor */ + private $returnProcessor; + + /** @var \PHPUnit_Framework_MockObject_MockObject|OrderItemInterface */ + private $orderItemMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject|StoreInterface */ + private $storeMock; + + public function setUp() + { + $this->stockManagementMock = $this->getMockBuilder(StockManagementInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockIndexerProcessorMock = $this->getMockBuilder( + \Magento\CatalogInventory\Model\Indexer\Stock\Processor::class + )->disableOriginalConstructor() + ->getMock(); + $this->priceIndexerMock = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price\Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderItemRepositoryMock = $this->getMockBuilder(OrderItemRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoItemMock = $this->getMockBuilder(CreditmemoItemInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderItemMock = $this->getMockBuilder(OrderItemInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->returnProcessor = new ReturnProcessor( + $this->stockManagementMock, + $this->stockIndexerProcessorMock, + $this->priceIndexerMock, + $this->creditmemoRepositoryMock, + $this->storeManagerMock, + $this->orderRepositoryMock, + $this->orderItemRepositoryMock + ); + } + + public function testExecute() + { + $orderItemId = 99; + $productId = 50; + $returnToStockItems = [$orderItemId]; + $qty = 1; + $storeId = 0; + $webSiteId = 10; + + $this->creditmemoMock->expects($this->once()) + ->method('getItems') + ->willReturn([$this->creditmemoItemMock]); + + $this->creditmemoItemMock->expects($this->once()) + ->method('getQty') + ->willReturn($qty); + + $this->creditmemoItemMock->expects($this->exactly(2)) + ->method('getOrderItemId') + ->willReturn($orderItemId); + + $this->creditmemoItemMock->expects($this->once()) + ->method('getProductId') + ->willReturn($productId); + + $this->orderItemRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderItemId) + ->willReturn($this->orderItemMock); + + $this->orderMock->expects($this->once()) + ->method('getStoreId') + ->willReturn($storeId); + + $this->storeManagerMock->expects($this->once()) + ->method('getStore') + ->with($storeId) + ->willReturn($this->storeMock); + + $this->storeMock->expects($this->once()) + ->method('getWebsiteId') + ->willReturn($webSiteId); + + $this->stockManagementMock->expects($this->once()) + ->method('backItemQty') + ->with($productId, $qty, $webSiteId) + ->willReturn(true); + + $this->stockIndexerProcessorMock->expects($this->once()) + ->method('reindexList') + ->with([$productId]); + + $this->priceIndexerMock->expects($this->once()) + ->method('reindexList') + ->with([$productId]); + + $this->returnProcessor->execute($this->creditmemoMock, $this->orderMock, $returnToStockItems); + } +} diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnValidatorTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..da9d3fd8aefd103965f2d577b303e7e1a522223b --- /dev/null +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Order/ReturnValidatorTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Test\Unit\Model\Order; + +use Magento\SalesInventory\Model\Order\ReturnValidator; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\CreditmemoItemInterface; +use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Api\OrderItemRepositoryInterface; + +/** + * Class ReturnValidatorTest + */ +class ReturnValidatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderItemRepositoryInterface + */ + private $orderItemRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoInterface + */ + private $creditMemoMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoItemInterface + */ + private $creditMemoItemMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderItemInterface + */ + private $orderItemMock; + + /** + * @var ReturnValidator + */ + private $returnValidator; + + protected function setUp() + { + $this->orderItemRepositoryMock = $this->getMockBuilder(OrderItemRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditMemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditMemoItemMock = $this->getMockBuilder(CreditmemoItemInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderItemMock = $this->getMockBuilder(OrderItemInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->returnValidator = new ReturnValidator( + $this->orderItemRepositoryMock + ); + } + + /** + * @dataProvider dataProvider + */ + public function testValidate( + $expectedResult, + $returnToStockItems, + $orderItemId, + $creditMemoItemId, + $productSku = null + ) { + $this->creditMemoMock->expects($this->once()) + ->method('getItems') + ->willReturn([$this->creditMemoItemMock]); + + $this->orderItemRepositoryMock->expects($this->once()) + ->method('get') + ->with($returnToStockItems[0]) + ->willReturn($this->orderItemMock); + + $this->orderItemMock->expects($this->once()) + ->method('getItemId') + ->willReturn($orderItemId); + + $this->creditMemoItemMock->expects($this->once()) + ->method('getOrderItemId') + ->willReturn($creditMemoItemId); + + if ($productSku) { + $this->orderItemMock->expects($this->once()) + ->method('getSku') + ->willReturn($productSku); + } + + $this->assertEquals( + $this->returnValidator->validate($returnToStockItems, $this->creditMemoMock), + $expectedResult + ); + } + + public function testValidationWithWrongOrderItems() + { + $returnToStockItems = [1]; + $this->creditMemoMock->expects($this->once()) + ->method('getItems') + ->willReturn([$this->creditMemoItemMock]); + $this->orderItemRepositoryMock->expects($this->once()) + ->method('get') + ->with($returnToStockItems[0]) + ->willThrowException(new NoSuchEntityException); + + $this->assertEquals( + $this->returnValidator->validate($returnToStockItems, $this->creditMemoMock), + __('The return to stock argument contains product item that is not part of the original order.') + ); + + } + + public function dataProvider() + { + return [ + 'PostirivValidationTest' => [null, [1], 1, 1], + 'WithWrongReturnToStockItems' => [ + __('The "%1" product is not part of the current creditmemo.', 'sku1'), [2], 2, 1, 'sku1', + ], + ]; + } +} diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/ReturnToStockInvoiceTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/ReturnToStockInvoiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..35718a96bd509cc8b021c286ca44c042a8a1d67d --- /dev/null +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/ReturnToStockInvoiceTest.php @@ -0,0 +1,179 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Test\Unit\Model\Plugin\Order; + +/** + * Class ReturnToStockInvoiceTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ReturnToStockInvoiceTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\SalesInventory\Model\Plugin\Order\ReturnToStockInvoice */ + private $returnTOStock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\SalesInventory\Model\Order\ReturnProcessor + */ + private $returnProcessorMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\CreditmemoRepositoryInterface + */ + private $creditmemoRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\InvoiceRepositoryInterface + */ + private $invoiceRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\OrderRepositoryInterface + */ + private $orderRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\RefundOrderInterface + */ + private $refundInvoiceMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface + */ + private $creditmemoCreationArgumentsMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\Data\OrderInterface + */ + private $orderMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\Data\CreditmemoInterface + */ + private $creditmemoMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\Data\InvoiceInterface + */ + private $invoiceMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface + */ + private $extencionAttributesMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\CatalogInventory\Api\StockConfigurationInterface + */ + private $stockConfigurationMock; + + protected function setUp() + { + $this->returnProcessorMock = $this->getMockBuilder(\Magento\SalesInventory\Model\Order\ReturnProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\CreditmemoRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\InvoiceRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->refundInvoiceMock = $this->getMockBuilder(\Magento\Sales\Api\RefundInvoiceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoCreationArgumentsMock = $this->getMockBuilder( + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface::class + )->disableOriginalConstructor() + ->getMock(); + $this->extencionAttributesMock = $this->getMockBuilder( + \Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface::class + )->disableOriginalConstructor() + ->setMethods(['getReturnToStockItems']) + ->getMock(); + $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockConfigurationMock = $this->getMockBuilder( + \Magento\CatalogInventory\Api\StockConfigurationInterface::class + )->disableOriginalConstructor() + ->getMock(); + + $this->returnTOStock = new \Magento\SalesInventory\Model\Plugin\Order\ReturnToStockInvoice( + $this->returnProcessorMock, + $this->creditmemoRepositoryMock, + $this->orderRepositoryMock, + $this->invoiceRepositoryMock, + $this->stockConfigurationMock + ); + } + + public function testAfterExecute() + { + $orderId = 1; + $creditmemoId = 99; + $items = []; + $returnToStockItems = [1]; + $invoiceId = 98; + $this->creditmemoCreationArgumentsMock->expects($this->exactly(3)) + ->method('getExtensionAttributes') + ->willReturn($this->extencionAttributesMock); + + $this->invoiceRepositoryMock->expects($this->once()) + ->method('get') + ->with($invoiceId) + ->willReturn($this->invoiceMock); + + $this->extencionAttributesMock->expects($this->exactly(2)) + ->method('getReturnToStockItems') + ->willReturn($returnToStockItems); + + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) + ->willReturn($this->orderMock); + + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('get') + ->with($creditmemoId) + ->willReturn($this->creditmemoMock); + + $this->returnProcessorMock->expects($this->once()) + ->method('execute') + ->with($this->creditmemoMock, $this->orderMock, $returnToStockItems); + + $this->invoiceMock->expects($this->once()) + ->method('getOrderId') + ->willReturn($orderId); + + $this->stockConfigurationMock->expects($this->once()) + ->method('isAutoReturnEnabled') + ->willReturn(false); + + $this->assertEquals( + $this->returnTOStock->afterExecute( + $this->refundInvoiceMock, + $creditmemoId, + $invoiceId, + $items, + false, + false, + false, + null, + $this->creditmemoCreationArgumentsMock + ), + $creditmemoId + ); + } +} diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/ReturnToStockOrderTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/ReturnToStockOrderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b4883dd1a7708a844e9c200162060fcac7cbf87a --- /dev/null +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/ReturnToStockOrderTest.php @@ -0,0 +1,158 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Test\Unit\Model\Plugin\Order; + +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\SalesInventory\Model\Order\ReturnProcessor; +use Magento\SalesInventory\Model\Plugin\Order\ReturnToStockOrder; +use Magento\Sales\Api\CreditmemoRepositoryInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\RefundOrderInterface; + +/** + * Class ReturnToStockOrderTest + */ +class ReturnToStockOrderTest extends \PHPUnit_Framework_TestCase +{ + /** @var ReturnToStockOrder */ + private $returnTOStock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|ReturnProcessor + */ + private $returnProcessorMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoRepositoryInterface + */ + private $creditmemoRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderRepositoryInterface + */ + private $orderRepositoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|RefundOrderInterface + */ + private $refundOrderMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoCreationArgumentsInterface + */ + private $creditmemoCreationArgumentsMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OrderInterface + */ + private $orderMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoInterface + */ + private $creditmemoMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|StockConfigurationInterface + */ + private $stockConfigurationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|CreditmemoCreationArgumentsInterface + */ + private $extencionAttributesMock; + + protected function setUp() + { + $this->returnProcessorMock = $this->getMockBuilder(ReturnProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoRepositoryMock = $this->getMockBuilder(CreditmemoRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->refundOrderMock = $this->getMockBuilder(RefundOrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->extencionAttributesMock = $this->getMockBuilder(CreditmemoCreationArgumentsExtensionInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getReturnToStockItems']) + ->getMock(); + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->stockConfigurationMock = $this->getMockBuilder(StockConfigurationInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->returnTOStock = new ReturnToStockOrder( + $this->returnProcessorMock, + $this->creditmemoRepositoryMock, + $this->orderRepositoryMock, + $this->stockConfigurationMock + ); + } + + public function testAfterExecute() + { + $orderId = 1; + $creditmemoId = 99; + $items = []; + $returnToStockItems = [1]; + $this->creditmemoCreationArgumentsMock->expects($this->exactly(3)) + ->method('getExtensionAttributes') + ->willReturn($this->extencionAttributesMock); + + $this->extencionAttributesMock->expects($this->exactly(2)) + ->method('getReturnToStockItems') + ->willReturn($returnToStockItems); + + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) + ->willReturn($this->orderMock); + + $this->creditmemoRepositoryMock->expects($this->once()) + ->method('get') + ->with($creditmemoId) + ->willReturn($this->creditmemoMock); + + $this->returnProcessorMock->expects($this->once()) + ->method('execute') + ->with($this->creditmemoMock, $this->orderMock, $returnToStockItems); + + $this->stockConfigurationMock->expects($this->once()) + ->method('isAutoReturnEnabled') + ->willReturn(false); + + $this->assertEquals( + $this->returnTOStock->afterExecute( + $this->refundOrderMock, + $creditmemoId, + $orderId, + $items, + false, + false, + null, + $this->creditmemoCreationArgumentsMock + ), + $creditmemoId + ); + } +} diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/Validation/InvoiceRefundCreationArgumentsTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/Validation/InvoiceRefundCreationArgumentsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d59da679d15e21331efcba024f05e0c5b0e05d4a --- /dev/null +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/Validation/InvoiceRefundCreationArgumentsTest.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Test\Unit\Model\Plugin\Order\Validation; + +use Magento\SalesInventory\Model\Order\ReturnValidator; +use Magento\SalesInventory\Model\Plugin\Order\Validation\InvoiceRefundCreationArguments; +use Magento\Sales\Model\ValidatorResultInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order\Validation\RefundInvoiceInterface; + +/** + * Class InvoiceRefundCreatetionArgumentsTest + */ +class InvoiceRefundCreationArgumentsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var InvoiceRefundCreationArguments + */ + private $plugin; + + /** + * @var ReturnValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $returnValidatorMock; + + /** + * @var CreditmemoCreationArgumentsExtensionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $extencionAttributesMock; + + /** + * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoCreationArgumentsMock; + + /** + * @var RefundInvoiceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $refundInvoiceValidatorMock; + + /** + * @var InvoiceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $invoiceMock; + + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $validateResultMock; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + protected function setUp() + { + $this->returnValidatorMock = $this->getMockBuilder(ReturnValidator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->extencionAttributesMock = $this->getMockBuilder(CreditmemoCreationArgumentsExtensionInterface::class) + ->setMethods(['getReturnToStockItems']) + ->disableOriginalConstructor() + ->getMock(); + + $this->validateResultMock = $this->getMockBuilder(ValidatorResultInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->refundInvoiceValidatorMock = $this->getMockBuilder(RefundInvoiceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->invoiceMock = $this->getMockBuilder(InvoiceInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new InvoiceRefundCreationArguments($this->returnValidatorMock); + } + + /** + * @dataProvider dataProvider + */ + public function testAfterValidation($erroMessage) + { + $returnToStockItems = [1]; + $this->creditmemoCreationArgumentsMock->expects($this->exactly(3)) + ->method('getExtensionAttributes') + ->willReturn($this->extencionAttributesMock); + + $this->extencionAttributesMock->expects($this->exactly(2)) + ->method('getReturnToStockItems') + ->willReturn($returnToStockItems); + + $this->returnValidatorMock->expects($this->once()) + ->method('validate') + ->willReturn($erroMessage); + + $this->validateResultMock->expects($erroMessage ? $this->once() : $this->never()) + ->method('addMessage') + ->with($erroMessage); + + $this->plugin->afterValidate( + $this->refundInvoiceValidatorMock, + $this->validateResultMock, + $this->invoiceMock, + $this->orderMock, + $this->creditmemoMock, + [], + false, + false, + false, + null, + $this->creditmemoCreationArgumentsMock + ); + } + + public function dataProvider() + { + return [ + 'withErrors' => ['Error!'], + 'withoutErrors' => ['null'], + ]; + } +} diff --git a/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/Validation/OrderRefundCreationArgumentsTest.php b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/Validation/OrderRefundCreationArgumentsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6a6b88f3d4580df6cde0479297968e12291e8d5d --- /dev/null +++ b/app/code/Magento/SalesInventory/Test/Unit/Model/Plugin/Order/Validation/OrderRefundCreationArgumentsTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Test\Unit\Model\Plugin\Order\Validation; + +use Magento\SalesInventory\Model\Order\ReturnValidator; +use Magento\SalesInventory\Model\Plugin\Order\Validation\OrderRefundCreationArguments; +use Magento\Sales\Model\Order\Validation\RefundOrderInterface; +use Magento\Sales\Model\ValidatorResultInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface; +use Magento\Sales\Api\Data\CreditmemoCreationArgumentsExtensionInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; + +/** + * Class OrderRefundCreatetionArgumentsTest + */ +class OrderRefundCreationArgumentsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var OrderRefundCreationArguments + */ + private $plugin; + + /** + * @var ReturnValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $returnValidatorMock; + + /** + * @var CreditmemoCreationArgumentsExtensionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $extencionAttributesMock; + + /** + * @var CreditmemoCreationArgumentsInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoCreationArgumentsMock; + + /** + * @var RefundOrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $refundOrderValidatorMock; + + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $validateResultMock; + + /** + * @var OrderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderMock; + + /** + * @var CreditmemoInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $creditmemoMock; + + protected function setUp() + { + $this->returnValidatorMock = $this->getMockBuilder(ReturnValidator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditmemoCreationArgumentsMock = $this->getMockBuilder(CreditmemoCreationArgumentsInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->extencionAttributesMock = $this->getMockBuilder(CreditmemoCreationArgumentsExtensionInterface::class) + ->setMethods(['getReturnToStockItems']) + ->disableOriginalConstructor() + ->getMock(); + + $this->validateResultMock = $this->getMockBuilder(ValidatorResultInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->refundOrderValidatorMock = $this->getMockBuilder(RefundOrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new OrderRefundCreationArguments($this->returnValidatorMock); + } + + /** + * @dataProvider dataProvider + */ + public function testAfterValidation($erroMessage) + { + $returnToStockItems = [1]; + $this->creditmemoCreationArgumentsMock->expects($this->exactly(3)) + ->method('getExtensionAttributes') + ->willReturn($this->extencionAttributesMock); + + $this->extencionAttributesMock->expects($this->exactly(2)) + ->method('getReturnToStockItems') + ->willReturn($returnToStockItems); + + $this->returnValidatorMock->expects($this->once()) + ->method('validate') + ->willReturn($erroMessage); + + $this->validateResultMock->expects($erroMessage ? $this->once() : $this->never()) + ->method('addMessage') + ->with($erroMessage); + + $this->plugin->afterValidate( + $this->refundOrderValidatorMock, + $this->validateResultMock, + $this->orderMock, + $this->creditmemoMock, + [], + false, + false, + null, + $this->creditmemoCreationArgumentsMock + ); + } + + /** + * @return array + */ + public function dataProvider() + { + return [ + 'withErrors' => ['Error!'], + 'withoutErrors' => ['null'], + ]; + } +} diff --git a/app/code/Magento/SalesInventory/composer.json b/app/code/Magento/SalesInventory/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..ff72ce7f0022690682b356235c506512e65497b9 --- /dev/null +++ b/app/code/Magento/SalesInventory/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-sales-inventory", + "description": "N/A", + "require": { + "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "magento/module-catalog-inventory": "100.2.*", + "magento/module-sales": "100.2.*", + "magento/module-store": "100.2.*", + "magento/module-catalog": "101.2.*", + "magento/framework": "100.2.*" + }, + "type": "magento2-module", + "version": "100.0.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\SalesInventory\\": "" + } + } +} diff --git a/app/code/Magento/SalesInventory/etc/di.xml b/app/code/Magento/SalesInventory/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..aec39d3c51c29d398e7c52f45bf5cf1443d3bffa --- /dev/null +++ b/app/code/Magento/SalesInventory/etc/di.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. 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\Sales\Model\RefundOrder"> + <plugin name="refundOrderAfter" type="Magento\SalesInventory\Model\Plugin\Order\ReturnToStockOrder"/> + </type> + <type name="Magento\Sales\Model\RefundInvoice"> + <plugin name="refundInvoiceAfter" type="Magento\SalesInventory\Model\Plugin\Order\ReturnToStockInvoice"/> + </type> + <type name="Magento\Sales\Model\Order\Validation\RefundOrderInterface"> + <plugin name="refundOrderValidationAfter" type="Magento\SalesInventory\Model\Plugin\Order\Validation\OrderRefundCreationArguments"/> + </type> + <type name="Magento\Sales\Model\Order\Validation\RefundInvoiceInterface"> + <plugin name="refundInvoiceValidationAfter" type="Magento\SalesInventory\Model\Plugin\Order\Validation\InvoiceRefundCreationArguments"/> + </type> +</config> diff --git a/app/code/Magento/SalesInventory/etc/extension_attributes.xml b/app/code/Magento/SalesInventory/etc/extension_attributes.xml new file mode 100644 index 0000000000000000000000000000000000000000..de5b6d41c52621f6ba1b2bf432b912157431bc30 --- /dev/null +++ b/app/code/Magento/SalesInventory/etc/extension_attributes.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd"> + <extension_attributes for="Magento\Sales\Api\Data\CreditmemoCreationArgumentsInterface"> + <attribute code="return_to_stock_items" type="int[]"/> + </extension_attributes> +</config> \ No newline at end of file diff --git a/app/code/Magento/SalesInventory/etc/module.xml b/app/code/Magento/SalesInventory/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..06c9d0d78554afed0c4ab3e2d81bdeaea03c4df9 --- /dev/null +++ b/app/code/Magento/SalesInventory/etc/module.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. 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_SalesInventory" setup_version="1.0.0"> + <sequence> + <module name="Magento_Sales"/> + <module name="Magento_Catalog"/> + <module name="Magento_Store"/> + <module name="Magento_CatalogInventory"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/SalesInventory/registration.php b/app/code/Magento/SalesInventory/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..edb96135508d2c61e5078ca771409a37a2c1bf30 --- /dev/null +++ b/app/code/Magento/SalesInventory/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_SalesInventory', + __DIR__ +); diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/Chooser.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/Chooser.php new file mode 100644 index 0000000000000000000000000000000000000000..b13ace25f020bfea737f6430916d957713ffa6f9 --- /dev/null +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Widget/Chooser.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesRule\Controller\Adminhtml\Promo\Widget; + +class Chooser extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Widget\Chooser +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_SalesRule::quote'; +} diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php index f85daffd3f9625b08520548c1d96697aad3e58cf..1670819588e6106c13226f6d19ab23edcad93bd0 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php @@ -51,4 +51,26 @@ class Product extends \Magento\Rule\Model\Condition\Product\AbstractProduct return parent::validate($product); } + + /** + * Retrieve value element chooser URL + * + * @return string + */ + public function getValueElementChooserUrl() + { + $url = false; + switch ($this->getAttribute()) { + case 'sku': + case 'category_ids': + $url = 'sales_rule/promo_widget/chooser/attribute/' . $this->getAttribute(); + if ($this->getJsFormObject()) { + $url .= '/form/' . $this->getJsFormObject(); + } + break; + default: + break; + } + return $url !== false ? $this->_backendData->getUrl($url) : ''; + } } diff --git a/app/code/Magento/SalesRule/Observer/SalesOrderAfterPlaceObserver.php b/app/code/Magento/SalesRule/Observer/SalesOrderAfterPlaceObserver.php index e352ede867a69638c00598d59fde6455be46c05a..a75947629db5d5a97b054306e7908b74cca300d4 100644 --- a/app/code/Magento/SalesRule/Observer/SalesOrderAfterPlaceObserver.php +++ b/app/code/Magento/SalesRule/Observer/SalesOrderAfterPlaceObserver.php @@ -57,7 +57,7 @@ class SalesOrderAfterPlaceObserver implements ObserverInterface { $order = $observer->getEvent()->getOrder(); - if (!$order || $order->getDiscountAmount() == 0) { + if (!$order || !$order->getAppliedRuleIds()) { return $this; } diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a28ea216eda32ed78b368cf3a7b84c53659904dd --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesRule\Test\Unit\Model\Rule\Condition; + +use \Magento\Rule\Model\Condition\Context; +use \Magento\Backend\Helper\Data; +use \Magento\Eav\Model\Config; +use \Magento\Catalog\Model\ProductFactory; +use \Magento\Catalog\Api\ProductRepositoryInterface; +use \Magento\Eav\Model\Entity\AbstractEntity; +use \Magento\Catalog\Model\ResourceModel\Product; +use \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection; +use \Magento\Framework\Locale\FormatInterface; +use \Magento\Eav\Model\Entity\AttributeLoaderInterface; +use \Magento\SalesRule\Model\Rule\Condition\Product as SalesRuleProduct; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ProductTest extends \PHPUnit_Framework_TestCase +{ + /** @var SalesRuleProduct */ + protected $model; + + /** @var Context|\PHPUnit_Framework_MockObject_MockObject */ + protected $contextMock; + + /** @var Data|\PHPUnit_Framework_MockObject_MockObject */ + protected $backendHelperMock; + + /** @var Config|\PHPUnit_Framework_MockObject_MockObject */ + protected $configMock; + + /** @var ProductFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $productFactoryMock; + + /** @var ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $productRepositoryMock; + + /** @var Product|\PHPUnit_Framework_MockObject_MockObject */ + protected $productMock; + + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject */ + protected $collectionMock; + + /** @var FormatInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $formatMock; + + /** @var AttributeLoaderInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $attributeLoaderInterfaceMock; + + /** + * Setup the test + */ + protected function setUp() + { + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->backendHelperMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productFactoryMock = $this->getMockBuilder(ProductFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productRepositoryMock = $this->getMockBuilder(ProductRepositoryInterface::class) + ->getMockForAbstractClass(); + $this->attributeLoaderInterfaceMock = $this->getMockBuilder(AbstractEntity::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributesByCode']) + ->getMock(); + $this->attributeLoaderInterfaceMock + ->expects($this->any()) + ->method('getAttributesByCode') + ->will($this->returnValue([])); + $this->productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['loadAllAttributes']) + ->getMock(); + $this->productMock + ->expects($this->any()) + ->method('loadAllAttributes') + ->will($this->returnValue($this->attributeLoaderInterfaceMock)); + $this->collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->formatMock = $this->getMockBuilder(FormatInterface::class) + ->getMockForAbstractClass(); + $this->model = new SalesRuleProduct( + $this->contextMock, + $this->backendHelperMock, + $this->configMock, + $this->productFactoryMock, + $this->productRepositoryMock, + $this->productMock, + $this->collectionMock, + $this->formatMock + ); + } + + /** + * @return array + */ + public function testGetValueElementChooserUrlDataProvider() + { + return [ + 'category_ids_without_js_object' => [ + 'category_ids', + 'sales_rule/promo_widget/chooser/attribute/' + ], + 'category_ids_with_js_object' => [ + 'category_ids', + 'sales_rule/promo_widget/chooser/attribute/', + 'jsobject' + ], + 'sku_without_js_object' => [ + 'sku', + 'sales_rule/promo_widget/chooser/attribute/', + 'jsobject' + ], + 'sku_without_with_js_object' => [ + 'sku', + 'sales_rule/promo_widget/chooser/attribute/' + ], + 'none' => [ + '', + '' + ] + ]; + } + + /** + * test getValueElementChooserUrl + * @param string $attribute + * @param string $url + * @param string $jsObject + * @dataProvider testGetValueElementChooserUrlDataProvider + */ + public function testGetValueElementChooserUrl($attribute, $url, $jsObject = '') + { + $this->model->setJsFormObject($jsObject); + $this->model->setAttribute($attribute); + $url .= $this->model->getAttribute(); + $this->backendHelperMock + ->expects($this->any()) + ->method('getUrl') + ->willReturnArgument(0); + + if ($this->model->getJsFormObject()) { + $url .= '/form/' . $this->model->getJsFormObject(); + } + + $this->assertEquals($url, $this->model->getValueElementChooserUrl()); + } +} diff --git a/app/code/Magento/SalesRule/Test/Unit/Observer/SalesOrderAfterPlaceObserverTest.php b/app/code/Magento/SalesRule/Test/Unit/Observer/SalesOrderAfterPlaceObserverTest.php index 0546dce56a3f6d6a27849708daf987c801e14a3c..c3d7e41acdf2801e3a6aaae7eb3f3db24d3a992c 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Observer/SalesOrderAfterPlaceObserverTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Observer/SalesOrderAfterPlaceObserverTest.php @@ -121,10 +121,10 @@ class SalesOrderAfterPlaceObserverTest extends \PHPUnit_Framework_TestCase { $observer = $this->getMock(\Magento\Framework\Event\Observer::class, [], [], '', false); $order = $this->initOrderFromEvent($observer); - $discountAmount = 10; + $ruleIds = null; $order->expects($this->once()) - ->method('getDiscountAmount') - ->will($this->returnValue($discountAmount)); + ->method('getAppliedRuleIds') + ->will($this->returnValue($ruleIds)); $this->ruleFactory->expects($this->never()) ->method('create'); @@ -158,14 +158,10 @@ class SalesOrderAfterPlaceObserverTest extends \PHPUnit_Framework_TestCase $ruleId = 1; $couponId = 1; $customerId = 1; - $discountAmount = 10; - $order->expects($this->once()) + $order->expects($this->exactly(2)) ->method('getAppliedRuleIds') ->will($this->returnValue($ruleId)); - $order->expects($this->once()) - ->method('getDiscountAmount') - ->will($this->returnValue($discountAmount)); $order->expects($this->once()) ->method('getCustomerId') ->will($this->returnValue($customerId)); diff --git a/app/code/Magento/SalesRule/etc/di.xml b/app/code/Magento/SalesRule/etc/di.xml index 2c731823b778fabe6faab37024f4e9ea60247538..83364f924397d857030a9f039ed1ab59dc0fd5f0 100644 --- a/app/code/Magento/SalesRule/etc/di.xml +++ b/app/code/Magento/SalesRule/etc/di.xml @@ -78,7 +78,7 @@ <type name="Magento\Framework\Model\Entity\RepositoryFactory"> <arguments> <argument name="entities" xsi:type="array"> - <item name="Magento\SalesRule\Api\Data\RuleInterface" xsi:type="string">Magento\SalesRule\Api\RuleRepositoryInterface</item> + <item name="Magento\SalesRule\Api\Data\RuleInterface" xsi:type="string">Magento\SalesRule\Model\ResourceModel\Rule</item> </argument> </arguments> </type> diff --git a/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/MetaTest.php b/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/MetaTest.php index 42f4d098ebcba34d91b6eac8944ebb5072040af3..7a842b8d1c908309c887b2fb5dc5f85f1e36213a 100644 --- a/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/MetaTest.php +++ b/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/MetaTest.php @@ -161,6 +161,7 @@ class MetaTest extends \PHPUnit_Framework_TestCase ->willReturn($metaId); $this->metaFactory->expects($this->once())->method('create')->willReturn($this->meta); $this->stepCheckSaveWithActiveProfile($metaData); + $this->meta->expects($this->once())->method('beforeLoad'); $this->assertEquals($this->meta, $this->resource->loadByEntityTypeAndStore($entityType, $storeId)); } @@ -177,7 +178,5 @@ class MetaTest extends \PHPUnit_Framework_TestCase ->method('quoteIdentifier'); $this->connectionMock->expects($this->once())->method('fetchRow')->willReturn($metaData); $this->resourceProfile->expects($this->once())->method('loadActiveProfile')->willReturn($this->profile); - $this->meta->expects($this->at(0))->method('setData')->with($metaData); - $this->meta->expects($this->at(2))->method('setData')->with('active_profile', $this->profile); } } diff --git a/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/ProfileTest.php b/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/ProfileTest.php index c3c827ba2f97a4e795a8f4d5633ebbf0736ba58e..b17445e7014fa618d2eb9c014f5a6b9bbc7d7f43 100644 --- a/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/ProfileTest.php +++ b/app/code/Magento/SalesSequence/Test/Unit/Model/ResourceModel/ProfileTest.php @@ -156,7 +156,7 @@ class ProfileTest extends \PHPUnit_Framework_TestCase $this->connectionMock->expects($this->any()) ->method('quoteIdentifier'); $this->connectionMock->expects($this->once())->method('fetchRow')->willReturn($profileData); - $this->profile->expects($this->at(0))->method('setData')->with($profileData); + $this->profile->expects($this->at(1))->method('setData')->with($profileData); $this->assertEquals($this->profile, $this->resource->loadActiveProfile($metaId)); } } diff --git a/app/code/Magento/Security/Model/Plugin/AccountManagement.php b/app/code/Magento/Security/Model/Plugin/AccountManagement.php index ba1d4af5618b706bbf2bf009568c526364c47108..c65442ec400209c8c789a7f3103cba6a6db01540 100644 --- a/app/code/Magento/Security/Model/Plugin/AccountManagement.php +++ b/app/code/Magento/Security/Model/Plugin/AccountManagement.php @@ -25,18 +25,26 @@ class AccountManagement */ protected $securityManager; + /** + * @var int + */ + protected $passwordRequestEvent; + /** * AccountManagement constructor. * * @param \Magento\Framework\App\RequestInterface $request * @param SecurityManager $securityManager + * @param int $passwordRequestEvent */ public function __construct( \Magento\Framework\App\RequestInterface $request, - \Magento\Security\Model\SecurityManager $securityManager + \Magento\Security\Model\SecurityManager $securityManager, + $passwordRequestEvent = PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST ) { $this->request = $request; $this->securityManager = $securityManager; + $this->passwordRequestEvent = $passwordRequestEvent; } /** @@ -56,7 +64,7 @@ class AccountManagement $websiteId = null ) { $this->securityManager->performSecurityCheck( - PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST, + $this->passwordRequestEvent, $email ); return [$email, $template, $websiteId]; diff --git a/app/code/Magento/Security/etc/adminhtml/di.xml b/app/code/Magento/Security/etc/adminhtml/di.xml index 4cbd3c3adc567188fef3b0b487d064264d5a1c12..c134638d1266e50442a7bb17b5ea2e7bb6bb42c8 100644 --- a/app/code/Magento/Security/etc/adminhtml/di.xml +++ b/app/code/Magento/Security/etc/adminhtml/di.xml @@ -15,6 +15,11 @@ <type name="Magento\Backend\Controller\Adminhtml\Auth\Login"> <plugin name="security_login_form" type="Magento\Security\Model\Plugin\LoginController" /> </type> + <type name="Magento\Security\Model\Plugin\AccountManagement"> + <arguments> + <argument name="passwordRequestEvent" xsi:type="const">Magento\Security\Model\PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST</argument> + </arguments> + </type> <type name="Magento\Security\Model\SecurityManager"> <arguments> <argument name="securityCheckers" xsi:type="array"> diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php index d265159bc630be220bc4a7a64b706e36786510ef..1c59c4033d377996da9d7b36dc5c9b0b6ba02fe8 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php @@ -107,6 +107,8 @@ class Save extends \Magento\Backend\App\Action $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setCommentText($data['comment_text']); } + $isNeedCreateLabel = isset($data['create_shipping_label']) && $data['create_shipping_label']; + try { $this->shipmentLoader->setOrderId($this->getRequest()->getParam('order_id')); $this->shipmentLoader->setShipmentId($this->getRequest()->getParam('shipment_id')); @@ -128,10 +130,12 @@ class Save extends \Magento\Backend\App\Action $shipment->setCustomerNote($data['comment_text']); $shipment->setCustomerNoteNotify(isset($data['comment_customer_notify'])); } - $errorMessages = $this->getShipmentValidator()->validate($shipment, [QuantityValidator::class]); - if (!empty($errorMessages)) { + $validationResult = $this->getShipmentValidator() + ->validate($shipment, [QuantityValidator::class]); + + if ($validationResult->hasMessages()) { $this->messageManager->addError( - __("Shipment Document Validation Error(s):\n" . implode("\n", $errorMessages)) + __("Shipment Document Validation Error(s):\n" . implode("\n", $validationResult->getMessages())) ); $this->_redirect('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); return; @@ -140,7 +144,6 @@ class Save extends \Magento\Backend\App\Action $shipment->getOrder()->setCustomerNoteNotify(!empty($data['send_email'])); $responseAjax = new \Magento\Framework\DataObject(); - $isNeedCreateLabel = isset($data['create_shipping_label']) && $data['create_shipping_label']; if ($isNeedCreateLabel) { $this->labelGenerator->create($shipment, $this->_request); diff --git a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php index f8d9c06dc8ec05c07c699c4b22e7b830c796e4dc..b1d2fc0d1d74456593b4cb4e3248e965b2033958 100644 --- a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php @@ -9,6 +9,7 @@ namespace Magento\Shipping\Test\Unit\Controller\Adminhtml\Order\Shipment; use Magento\Backend\App\Action; +use Magento\Sales\Model\ValidatorResultInterface; use Magento\Sales\Model\Order\Email\Sender\ShipmentSender; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface; @@ -18,6 +19,7 @@ use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator; * Class SaveTest * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class SaveTest extends \PHPUnit_Framework_TestCase { @@ -96,6 +98,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase */ private $shipmentValidatorMock; + /** + * @var ValidatorResultInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $validationResult; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -107,6 +114,9 @@ class SaveTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods([]) ->getMock(); + $this->validationResult = $this->getMockBuilder(ValidatorResultInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->labelGenerator = $this->getMockBuilder(\Magento\Shipping\Model\Shipping\LabelGenerator::class) ->disableOriginalConstructor() ->setMethods([]) @@ -362,7 +372,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $this->shipmentValidatorMock->expects($this->once()) ->method('validate') ->with($shipment, [QuantityValidator::class]) - ->willReturn([]); + ->willReturn($this->validationResult); + + $this->validationResult->expects($this->once()) + ->method('hasMessages') + ->willReturn(false); $this->saveAction->execute(); $this->assertEquals($this->response, $this->saveAction->getResponse()); diff --git a/app/code/Magento/Theme/view/frontend/web/js/view/messages.js b/app/code/Magento/Theme/view/frontend/web/js/view/messages.js index 48507343fe28dc928edf9e6af2038bfe29577216..b707659fb3f4c790eef6df14fd8e6c9d68eeef1d 100644 --- a/app/code/Magento/Theme/view/frontend/web/js/view/messages.js +++ b/app/code/Magento/Theme/view/frontend/web/js/view/messages.js @@ -32,7 +32,7 @@ define([ customerData.set('messages', {}); } - $.cookieStorage.setConf({path: '/', expires: -1}).set('mage-messages', null); + $.cookieStorage.set('mage-messages', ''); } }); }); diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php index de363262275e59386b792388567a27038a86e021..e9c0c8d7685166001b524af995d62bf6b3c84011 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php @@ -32,6 +32,8 @@ class Date extends AbstractDataType protected $wrappedComponent; /** + * Constructor + * * @param ContextInterface $context * @param TimezoneInterface $localeDate * @param ResolverInterface $localeResolver @@ -58,23 +60,15 @@ class Date extends AbstractDataType public function prepare() { $config = $this->getData('config'); - - if (!isset($config['timeOffset'])) { - $config['timeOffset'] = (new \DateTime( - 'now', - new \DateTimeZone( - $this->localeDate->getConfigTimezone() - ) - ))->getOffset(); + if (!isset($config['storeTimeZone'])) { + $storeTimeZone = $this->localeDate->getConfigTimezone(); + $config['storeTimeZone'] = $storeTimeZone; } - // Set date format pattern by current locale $localeDateFormat = $this->localeDate->getDateFormat(); $config['options']['dateFormat'] = $localeDateFormat; $config['outputDateFormat'] = $localeDateFormat; - $this->setData('config', $config); - parent::prepare(); } diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/DateTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/DateTest.php index ef0743aa32947c7f49690c2f7c9ee1d3c5aa1119..7a9df1a7df0a42cf40e98b2c7b0f115d78d5301a 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/DateTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/DataType/DateTest.php @@ -6,54 +6,48 @@ namespace Magento\Ui\Test\Unit\Component\Form\Element\DataType; use Magento\Ui\Component\Form\Element\DataType\Date; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\UiComponent\Context; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\View\Element\UiComponent\Processor; class DateTest extends \PHPUnit_Framework_TestCase { - /** - * @var \Magento\Framework\View\Element\UiComponent\ContextInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $context; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $contextMock; - /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $localeDate; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $localeDateMock; - /** - * @var \Magento\Framework\Locale\ResolverInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $localeResolver; + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $localeResolverMock; - /** - * @var Date - */ - private $model; - - public function setUp() - { - $processorMock = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) - ->disableOriginalConstructor() - ->getMock(); + /** @var \Magento\Ui\Component\Form\Element\DataType\Date */ + private $date; - $this->context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) - ->getMockForAbstractClass(); - $this->context->expects($this->any()) - ->method('getProcessor') - ->willReturn($processorMock); + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $processorMock; - $this->localeDate = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class) - ->getMockForAbstractClass(); + /** @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ + private $objectManagerHelper; - $this->localeResolver = $this->getMockBuilder(\Magento\Framework\Locale\ResolverInterface::class) - ->getMockForAbstractClass(); + public function setUp() + { + $this->contextMock = $this->getMock(Context::class, [], [], '', false); + $this->localeDateMock = $this->getMock(TimezoneInterface::class, [], [], '', false); + $this->localeResolverMock = $this->getMock(ResolverInterface::class, [], [], '', false); + $this->objectManagerHelper = new ObjectManager($this); + $this->processorMock = $this->getMock(Processor::class, [], [], '', false); + $this->contextMock->expects($this->any())->method('getProcessor')->willReturn($this->processorMock); } public function testPrepareWithTimeOffset() { - $this->model = new Date( - $this->context, - $this->localeDate, - $this->localeResolver, + $this->date = new Date( + $this->contextMock, + $this->localeDateMock, + $this->localeResolverMock, [], [ 'config' => [ @@ -64,13 +58,13 @@ class DateTest extends \PHPUnit_Framework_TestCase $localeDateFormat = 'dd/MM/y'; - $this->localeDate->expects($this->once()) + $this->localeDateMock->expects($this->once()) ->method('getDateFormat') ->willReturn($localeDateFormat); - $this->model->prepare(); + $this->date->prepare(); - $config = $this->model->getConfig(); + $config = $this->date->getConfig(); $this->assertTrue(is_array($config)); $this->assertArrayHasKey('options', $config); @@ -85,10 +79,10 @@ class DateTest extends \PHPUnit_Framework_TestCase { $defaultDateFormat = 'MM/dd/y'; - $this->model = new Date( - $this->context, - $this->localeDate, - $this->localeResolver, + $this->date = new Date( + $this->contextMock, + $this->localeDateMock, + $this->localeResolverMock, [], [ 'config' => [ @@ -102,20 +96,18 @@ class DateTest extends \PHPUnit_Framework_TestCase $localeDateFormat = 'dd/MM/y'; - $this->localeDate->expects($this->once()) + $this->localeDateMock->expects($this->once()) ->method('getDateFormat') ->willReturn($localeDateFormat); - $this->localeDate->expects($this->once()) + $this->localeDateMock->expects($this->any()) ->method('getConfigTimezone') ->willReturn('America/Los_Angeles'); - $this->model->prepare(); + $this->date->prepare(); - $config = $this->model->getConfig(); + $config = $this->date->getConfig(); $this->assertTrue(is_array($config)); - $this->assertArrayHasKey('timeOffset', $config); - $this->assertArrayHasKey('options', $config); $this->assertArrayHasKey('dateFormat', $config['options']); $this->assertEquals($localeDateFormat, $config['options']['dateFormat']); @@ -123,4 +115,23 @@ class DateTest extends \PHPUnit_Framework_TestCase $this->assertArrayHasKey('outputDateFormat', $config); $this->assertEquals($localeDateFormat, $config['outputDateFormat']); } + + /** + * This tests ensures that userTimeZone is properly saved in the configuration + */ + public function testPrepare() + { + $this->date = $this->objectManagerHelper->getObject( + Date::class, + [ + 'context' => $this->contextMock, + 'localeDate' => $this->localeDateMock, + 'localeResolver' => $this->localeResolverMock + ] + ); + $this->localeDateMock->expects($this->any())->method('getConfigTimezone')->willReturn('America/Chicago'); + $this->date->prepare(); + $configArray = $this->date->getData('config'); + $this->assertEquals('America/Chicago', $configArray['storeTimeZone']); + } } diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/date.js b/app/code/Magento/Ui/view/base/web/js/form/element/date.js index 1c9f0b9b3e81c40fd07137af298e91b4c62412e4..fde7faa72ed7c9307e0b2781b7301d421efe2fc0 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/date.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/date.js @@ -5,7 +5,8 @@ define([ 'moment', 'mageUtils', - './abstract' + './abstract', + 'moment-timezone-with-data' ], function (moment, utils, Abstract) { 'use strict'; @@ -13,7 +14,7 @@ define([ defaults: { options: {}, - timeOffset: 0, + storeTimeZone: 'UTC', validationParams: { dateFormat: '${ $.outputDateFormat }' @@ -61,7 +62,7 @@ define([ /** * Date/time value shifted to corresponding timezone - * according to this.timeOffset property. This value + * according to this.storeTimeZone property. This value * will be sent to the server. * * @type {String} @@ -109,7 +110,7 @@ define([ if (value) { if (this.options.showsTime) { - shiftedValue = moment.utc(value).add(this.timeOffset, 'seconds'); + shiftedValue = moment.tz(value, 'UTC').tz(this.storeTimeZone); } else { dateFormat = this.shiftedValue() ? this.outputDateFormat : this.inputDateFormat; @@ -133,12 +134,13 @@ define([ * @param {String} shiftedValue */ onShiftedValueChange: function (shiftedValue) { - var value; + var value, + formattedValue; if (shiftedValue) { if (this.options.showsTime) { - value = moment.utc(shiftedValue, this.pickerDateTimeFormat); - value = value.subtract(this.timeOffset, 'seconds').toISOString(); + formattedValue = moment(shiftedValue).format('YYYY-MM-DD HH:mm'); + value = moment.tz(formattedValue, this.storeTimeZone).tz('UTC').toISOString(); } else { value = moment(shiftedValue, this.pickerDateTimeFormat); value = value.format(this.outputDateFormat); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js b/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js index d87b6b6b665ca0d070287474edb41eb5ced41c33..dca12f832cd15e1cdc197ec909df92557c45da60 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js @@ -79,6 +79,10 @@ define([ getData: function (params, options) { var cachedRequest = this.getRequest(params); + if (params && params.filters && params.filters['store_id']) { + cachedRequest = false; + } + options = options || {}; return !options.refresh && cachedRequest ? diff --git a/app/code/Magento/Ui/view/base/web/js/grid/provider.js b/app/code/Magento/Ui/view/base/web/js/grid/provider.js index 92a4a6fd38cc52ce93f4cce45dc823f5fa15d64e..7bca45d0253d047b056e7915d10a7ede491f93b6 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/provider.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/provider.js @@ -43,6 +43,10 @@ define([ .initStorage() .clearData(); + // Load data when there will + // be no more pending assets. + resolver(this.reload, this); + return this; }, @@ -122,9 +126,11 @@ define([ * Handles changes of 'params' object. */ onParamsChange: function () { - this.firstLoad ? - resolver(this.reload, this) : + // It's necessary to make a reload only + // after the initial loading has been made. + if (!this.firstLoad) { this.reload(); + } }, /** diff --git a/app/code/Magento/Vault/Api/PaymentMethodListInterface.php b/app/code/Magento/Vault/Api/PaymentMethodListInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f9d3e628594ef0f809429f39564271e6f52e7b23 --- /dev/null +++ b/app/code/Magento/Vault/Api/PaymentMethodListInterface.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Vault\Api; + +use Magento\Vault\Model\VaultPaymentInterface; + +/** + * Contains methods to retrieve vault payment methods + * This interface is consistent with \Magento\Payment\Api\PaymentMethodListInterface + * @api + */ +interface PaymentMethodListInterface +{ + /** + * Get list of available vault payments + * @param int $storeId + * @return VaultPaymentInterface[] + */ + public function getList($storeId); + + /** + * Get list of enabled in the configuration vault payments + * @param int $storeId + * @return VaultPaymentInterface[] + */ + public function getActiveList($storeId); +} diff --git a/app/code/Magento/Vault/Model/PaymentMethodList.php b/app/code/Magento/Vault/Model/PaymentMethodList.php new file mode 100644 index 0000000000000000000000000000000000000000..bec073df7971154ee74cd0b794d4cd11d347c832 --- /dev/null +++ b/app/code/Magento/Vault/Model/PaymentMethodList.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Vault\Model; + +use Magento\Payment\Api\Data\PaymentMethodInterface; +use Magento\Payment\Api\PaymentMethodListInterface; +use Magento\Payment\Model\Method\InstanceFactory; +use Magento\Payment\Model\MethodInterface; +use Magento\Vault\Api\PaymentMethodListInterface as VaultPaymentMethodListInterface; + +/** + * Contains methods to retrieve configured vault payments + */ +class PaymentMethodList implements VaultPaymentMethodListInterface +{ + /** + * @var InstanceFactory + */ + private $instanceFactory; + + /** + * @var PaymentMethodListInterface + */ + private $paymentMethodList; + + /** + * PaymentMethodList constructor. + * @param PaymentMethodListInterface $paymentMethodList + * @param InstanceFactory $instanceFactory + */ + public function __construct(PaymentMethodListInterface $paymentMethodList, InstanceFactory $instanceFactory) + { + $this->instanceFactory = $instanceFactory; + $this->paymentMethodList = $paymentMethodList; + } + + /** + * @inheritdoc + */ + public function getList($storeId) + { + return $this->filterList($this->paymentMethodList->getList($storeId)); + } + + /** + * @inheritdoc + */ + public function getActiveList($storeId) + { + return $this->filterList($this->paymentMethodList->getActiveList($storeId)); + } + + /** + * Filter vault methods from payments + * @param PaymentMethodInterface[] $list + * @return VaultPaymentInterface[] + */ + private function filterList(array $list) + { + $paymentMethods = array_map( + function (PaymentMethodInterface $paymentMethod) { + return $this->instanceFactory->create($paymentMethod); + }, + $list + ); + + $availableMethods = array_filter( + $paymentMethods, + function (MethodInterface $methodInstance) { + return $methodInstance instanceof VaultPaymentInterface; + } + ); + return array_values($availableMethods); + } +} diff --git a/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php b/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php index 060e16d1c3f1aa14e909dc3662315a35f4da680d..40343c29620e1a5c9ecc6e9779baf77336a09481 100644 --- a/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php +++ b/app/code/Magento/Vault/Model/Ui/TokensConfigProvider.php @@ -7,10 +7,9 @@ namespace Magento\Vault\Model\Ui; use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Framework\App\ObjectManager; -use Magento\Payment\Api\Data\PaymentMethodInterface; use Magento\Store\Model\StoreManagerInterface; +use Magento\Vault\Api\PaymentMethodListInterface; use Magento\Vault\Model\CustomerTokenManagement; -use Magento\Vault\Model\VaultPaymentInterface; /** * Class ConfigProvider @@ -39,14 +38,9 @@ final class TokensConfigProvider implements ConfigProviderInterface private $customerTokenManagement; /** - * @var \Magento\Payment\Api\PaymentMethodListInterface + * @var PaymentMethodListInterface */ - private $paymentMethodList; - - /** - * @var \Magento\Payment\Model\Method\InstanceFactory - */ - private $paymentMethodInstanceFactory; + private $vaultPaymentList; /** * Constructor @@ -112,7 +106,8 @@ final class TokensConfigProvider implements ConfigProviderInterface private function getComponentProviders() { $providers = []; - $vaultPaymentMethods = $this->getVaultPaymentMethodList(); + $storeId = $this->storeManager->getStore()->getId(); + $vaultPaymentMethods = $this->getVaultPaymentList()->getActiveList($storeId); foreach ($vaultPaymentMethods as $method) { $providerCode = $method->getProviderCode(); @@ -141,60 +136,15 @@ final class TokensConfigProvider implements ConfigProviderInterface } /** - * Get list of active Vault payment methods. - * - * @return VaultPaymentInterface[] - */ - private function getVaultPaymentMethodList() - { - $storeId = $this->storeManager->getStore()->getId(); - - $paymentMethods = array_map( - function (PaymentMethodInterface $paymentMethod) { - return $this->getPaymentMethodInstanceFactory()->create($paymentMethod); - }, - $this->getPaymentMethodList()->getActiveList($storeId) - ); - - $availableMethods = array_filter( - $paymentMethods, - function (\Magento\Payment\Model\MethodInterface $methodInstance) { - return $methodInstance instanceof VaultPaymentInterface; - } - ); - - return $availableMethods; - } - - /** - * Get payment method list. - * - * @return \Magento\Payment\Api\PaymentMethodListInterface - * @deprecated - */ - private function getPaymentMethodList() - { - if ($this->paymentMethodList === null) { - $this->paymentMethodList = ObjectManager::getInstance()->get( - \Magento\Payment\Api\PaymentMethodListInterface::class - ); - } - return $this->paymentMethodList; - } - - /** - * Get payment method instance factory. - * - * @return \Magento\Payment\Model\Method\InstanceFactory + * Get instance of vault payment list instance + * @return PaymentMethodListInterface * @deprecated */ - private function getPaymentMethodInstanceFactory() + private function getVaultPaymentList() { - if ($this->paymentMethodInstanceFactory === null) { - $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( - \Magento\Payment\Model\Method\InstanceFactory::class - ); + if ($this->vaultPaymentList === null) { + $this->vaultPaymentList = ObjectManager::getInstance()->get(PaymentMethodListInterface::class); } - return $this->paymentMethodInstanceFactory; + return $this->vaultPaymentList; } } diff --git a/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php b/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php index 9cd7b97562df98e309b9ddd182a5ced144b2e555..bc3110a101452b40deb5506bc659d88a5f4e34a1 100644 --- a/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php +++ b/app/code/Magento/Vault/Model/Ui/VaultConfigProvider.php @@ -8,9 +8,8 @@ namespace Magento\Vault\Model\Ui; use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Session\SessionManagerInterface; -use Magento\Payment\Api\Data\PaymentMethodInterface; use Magento\Store\Model\StoreManagerInterface; -use Magento\Vault\Model\VaultPaymentInterface; +use Magento\Vault\Api\PaymentMethodListInterface; class VaultConfigProvider implements ConfigProviderInterface { @@ -32,14 +31,9 @@ class VaultConfigProvider implements ConfigProviderInterface private $session; /** - * @var \Magento\Payment\Api\PaymentMethodListInterface + * @var PaymentMethodListInterface */ - private $paymentMethodList; - - /** - * @var \Magento\Payment\Model\Method\InstanceFactory - */ - private $paymentMethodInstanceFactory; + private $vaultPaymentList; /** * VaultConfigProvider constructor. @@ -62,9 +56,9 @@ class VaultConfigProvider implements ConfigProviderInterface public function getConfig() { $availableMethods = []; - $vaultPayments = $this->getVaultPaymentMethodList(); - $customerId = $this->session->getCustomerId(); $storeId = $this->storeManager->getStore()->getId(); + $vaultPayments = $this->getVaultPaymentList()->getActiveList($storeId); + $customerId = $this->session->getCustomerId(); foreach ($vaultPayments as $method) { $availableMethods[$method->getCode()] = [ @@ -78,60 +72,15 @@ class VaultConfigProvider implements ConfigProviderInterface } /** - * Get list of active Vault payment methods. - * - * @return VaultPaymentInterface[] - */ - private function getVaultPaymentMethodList() - { - $storeId = $this->storeManager->getStore()->getId(); - - $paymentMethods = array_map( - function (PaymentMethodInterface $paymentMethod) { - return $this->getPaymentMethodInstanceFactory()->create($paymentMethod); - }, - $this->getPaymentMethodList()->getActiveList($storeId) - ); - - $availableMethods = array_filter( - $paymentMethods, - function (\Magento\Payment\Model\MethodInterface $methodInstance) { - return $methodInstance instanceof VaultPaymentInterface; - } - ); - - return $availableMethods; - } - - /** - * Get payment method list. - * - * @return \Magento\Payment\Api\PaymentMethodListInterface - * @deprecated - */ - private function getPaymentMethodList() - { - if ($this->paymentMethodList === null) { - $this->paymentMethodList = ObjectManager::getInstance()->get( - \Magento\Payment\Api\PaymentMethodListInterface::class - ); - } - return $this->paymentMethodList; - } - - /** - * Get payment method instance factory. - * - * @return \Magento\Payment\Model\Method\InstanceFactory + * Get vault payment list instance + * @return PaymentMethodListInterface * @deprecated */ - private function getPaymentMethodInstanceFactory() + private function getVaultPaymentList() { - if ($this->paymentMethodInstanceFactory === null) { - $this->paymentMethodInstanceFactory = ObjectManager::getInstance()->get( - \Magento\Payment\Model\Method\InstanceFactory::class - ); + if ($this->vaultPaymentList === null) { + $this->vaultPaymentList = ObjectManager::getInstance()->get(PaymentMethodListInterface::class); } - return $this->paymentMethodInstanceFactory; + return $this->vaultPaymentList; } } diff --git a/app/code/Magento/Vault/Test/Unit/Model/PaymentMethodListTest.php b/app/code/Magento/Vault/Test/Unit/Model/PaymentMethodListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9e9d426fa8f3a487335f1f3fd0242833fbcc6e9d --- /dev/null +++ b/app/code/Magento/Vault/Test/Unit/Model/PaymentMethodListTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Vault\Test\Unit\Model; + +use Magento\Payment\Api\Data\PaymentMethodInterface; +use Magento\Payment\Api\PaymentMethodListInterface; +use Magento\Payment\Model\Method\InstanceFactory; +use Magento\Payment\Model\MethodInterface; +use Magento\Vault\Model\VaultPaymentInterface; +use Magento\Vault\Model\PaymentMethodList; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class PaymentMethodListTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PaymentMethodListInterface|MockObject + */ + private $paymentMethodList; + + /** + * @var InstanceFactory|MockObject + */ + private $instanceFactory; + + /** + * @var PaymentMethodList + */ + private $vaultPaymentList; + + protected function setUp() + { + $this->paymentMethodList = $this->getMock(PaymentMethodListInterface::class); + $this->instanceFactory = $this->getMockBuilder(InstanceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->vaultPaymentList = new PaymentMethodList($this->paymentMethodList, $this->instanceFactory); + } + + /** + * @covers \Magento\Vault\Model\PaymentMethodList::getActiveList + */ + public function testGetActivePaymentList() + { + $storeId = 1; + $vaultPayment = $this->getMock(VaultPaymentInterface::class); + $paymentMethodInterface1 = $this->getMock(PaymentMethodInterface::class); + $paymentMethodInterface2 = $this->getMock(PaymentMethodInterface::class); + $activePayments = [ + $paymentMethodInterface1, + $paymentMethodInterface2 + ]; + + $this->paymentMethodList->expects(static::once()) + ->method('getActiveList') + ->with($storeId) + ->willReturn($activePayments); + + $this->instanceFactory->expects(static::exactly(2)) + ->method('create') + ->willReturnMap([ + [$paymentMethodInterface1, $this->getMock(MethodInterface::class)], + [$paymentMethodInterface2, $vaultPayment] + ]); + + $vaultPayments = $this->vaultPaymentList->getActiveList($storeId); + static::assertCount(1, $vaultPayments); + static::assertInstanceOf(VaultPaymentInterface::class, $vaultPayment); + } +} diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php index d5ac3cac9563d4a8d8d4f294eca2dfaab8f44374..34b7c5240497164327cadac60fc00a33eb390c58 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/TokensConfigProviderTest.php @@ -5,11 +5,11 @@ */ namespace Magento\Vault\Test\Unit\Model\Ui; -use Magento\Customer\Model\Session; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Api\PaymentMethodListInterface; use Magento\Vault\Model\CustomerTokenManagement; use Magento\Vault\Model\Ui\TokensConfigProvider; use Magento\Vault\Model\Ui\TokenUiComponentInterface; @@ -31,25 +31,10 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase */ private $storeManager; - /** - * @var \Magento\Payment\Api\PaymentMethodListInterface|MockObject - */ - private $paymentMethodList; - - /** - * @var \Magento\Payment\Model\Method\InstanceFactory|MockObject - */ - private $paymentMethodInstanceFactory; - - /** - * @var \Magento\Payment\Api\Data\PaymentMethodInterface|MockObject - */ - private $vaultPayment; - /** * @var VaultPaymentInterface|MockObject */ - private $vaultPaymentInstance; + private $vaultPayment; /** * @var StoreInterface|MockObject @@ -61,6 +46,11 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase */ private $customerTokenManagement; + /** + * @var PaymentMethodListInterface|MockObject + */ + private $vaultPaymentList; + /** * @var ObjectManager */ @@ -68,20 +58,12 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $this->paymentMethodInstanceFactory = $this->getMockBuilder( - \Magento\Payment\Model\Method\InstanceFactory::class - )->disableOriginalConstructor()->getMock(); - - $this->vaultPayment = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class); - $this->vaultPaymentInstance = $this->getMockForAbstractClass(VaultPaymentInterface::class); + $this->objectManager = new ObjectManager($this); + $this->vaultPaymentList = $this->getMock(PaymentMethodListInterface::class); + $this->vaultPayment = $this->getMockForAbstractClass(VaultPaymentInterface::class); $this->storeManager = $this->getMock(StoreManagerInterface::class); $this->store = $this->getMock(StoreInterface::class); - $this->objectManager = new ObjectManager($this); $this->customerTokenManagement = $this->getMockBuilder(CustomerTokenManagement::class) ->disableOriginalConstructor() ->getMock(); @@ -114,16 +96,12 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase ->method('getId') ->willReturn($storeId); - $this->paymentMethodList->expects(static::once()) + $this->vaultPaymentList->expects(static::once()) ->method('getActiveList') ->with($storeId) ->willReturn([$this->vaultPayment]); - - $this->paymentMethodInstanceFactory->expects($this->once()) - ->method('create') - ->willReturn($this->vaultPaymentInstance); - $this->vaultPaymentInstance->expects(static::once()) + $this->vaultPayment->expects(static::once()) ->method('getProviderCode') ->willReturn($vaultProviderCode); @@ -153,16 +131,10 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase $vaultProviderCode => $tokenUiComponentProvider ] ); - - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'paymentMethodList', - $this->paymentMethodList - ); $this->objectManager->setBackwardCompatibleProperty( $configProvider, - 'paymentMethodInstanceFactory', - $this->paymentMethodInstanceFactory + 'vaultPaymentList', + $this->vaultPaymentList ); static::assertEquals($expectedConfig, $configProvider->getConfig()); diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php index d00531637b86d4e509662a31ce2da32752ba4a8b..e9d6af3bec356f518fbea6b1ce8be5ab841f78d4 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/VaultConfigProviderTest.php @@ -9,35 +9,20 @@ use Magento\Customer\Model\Session; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Model\StoreManagerInterface; +use Magento\Vault\Api\PaymentMethodListInterface; use Magento\Vault\Model\Ui\VaultConfigProvider; use Magento\Vault\Model\VaultPaymentInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class VaultConfigProviderTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase { - /** - * @var \Magento\Payment\Api\PaymentMethodListInterface|MockObject - */ - private $paymentMethodList; - - /** - * @var \Magento\Payment\Model\Method\InstanceFactory|MockObject - */ - private $paymentMethodInstanceFactory; - - /** - * @var \Magento\Payment\Api\Data\PaymentMethodInterface|MockObject - */ - private $vaultPayment; - /** * @var VaultPaymentInterface|MockObject */ - private $vaultPaymentInstance; + private $vaultPayment; /** * @var Session|MockObject @@ -54,6 +39,11 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase */ private $storeManager; + /** + * @var PaymentMethodListInterface|MockObject + */ + private $vaultPaymentList; + /** * @var VaultConfigProvider */ @@ -61,33 +51,20 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->paymentMethodList = $this->getMockBuilder(\Magento\Payment\Api\PaymentMethodListInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $this->paymentMethodInstanceFactory = $this->getMockBuilder( - \Magento\Payment\Model\Method\InstanceFactory::class - )->disableOriginalConstructor()->getMock(); - - $this->vaultPayment = $this->getMockForAbstractClass(\Magento\Payment\Api\Data\PaymentMethodInterface::class); - $this->vaultPaymentInstance = $this->getMockForAbstractClass(VaultPaymentInterface::class); + $this->vaultPayment = $this->getMockForAbstractClass(VaultPaymentInterface::class); $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); $this->store = $this->getMockForAbstractClass(StoreInterface::class); $this->session = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() ->getMock(); + $this->vaultPaymentList = $this->getMock(PaymentMethodListInterface::class); $objectManager = new ObjectManager($this); $this->vaultConfigProvider = new VaultConfigProvider($this->storeManager, $this->session); $objectManager->setBackwardCompatibleProperty( $this->vaultConfigProvider, - 'paymentMethodList', - $this->paymentMethodList - ); - $objectManager->setBackwardCompatibleProperty( - $this->vaultConfigProvider, - 'paymentMethodInstanceFactory', - $this->paymentMethodInstanceFactory + 'vaultPaymentList', + $this->vaultPaymentList ); } @@ -112,26 +89,21 @@ class VaultConfigProviderTest extends \PHPUnit_Framework_TestCase $this->session->expects(static::once()) ->method('getCustomerId') ->willReturn($customerId); - $this->storeManager->expects(static::exactly(2)) + $this->storeManager->expects(static::once()) ->method('getStore') ->willReturn($this->store); - $this->store->expects(static::exactly(2)) + $this->store->expects(static::once()) ->method('getId') ->willReturn($storeId); - $this->paymentMethodList->expects(static::once()) + $this->vaultPaymentList->expects(static::once()) ->method('getActiveList') - ->with($storeId) ->willReturn([$this->vaultPayment]); - $this->paymentMethodInstanceFactory->expects($this->once()) - ->method('create') - ->willReturn($this->vaultPaymentInstance); - - $this->vaultPaymentInstance->expects(static::once()) + $this->vaultPayment->expects(static::once()) ->method('getCode') ->willReturn($vaultPaymentCode); - $this->vaultPaymentInstance->expects($customerId !== null ? static::once() : static::never()) + $this->vaultPayment->expects($customerId !== null ? static::once() : static::never()) ->method('isActive') ->with($storeId) ->willReturn($vaultEnabled); diff --git a/app/code/Magento/Vault/etc/di.xml b/app/code/Magento/Vault/etc/di.xml index e44e1da3e3d61f3c5d230c978b798fa05e84db7e..14354da7e2c528e910a0af6bd15706f25dbeadb4 100644 --- a/app/code/Magento/Vault/etc/di.xml +++ b/app/code/Magento/Vault/etc/di.xml @@ -11,6 +11,7 @@ <preference for="Magento\Vault\Api\Data\PaymentTokenInterface" type="Magento\Vault\Model\PaymentToken"/> <preference for="Magento\Vault\Api\PaymentTokenRepositoryInterface" type="Magento\Vault\Model\PaymentTokenRepository" /> <preference for="Magento\Vault\Api\PaymentTokenManagementInterface" type="Magento\Vault\Model\PaymentTokenManagement" /> + <preference for="Magento\Vault\Api\PaymentMethodListInterface" type="Magento\Vault\Model\PaymentMethodList" /> <preference for="Magento\Vault\Api\Data\PaymentTokenSearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\Vault\Model\Ui\TokenUiComponentInterface" type="Magento\Vault\Model\Ui\TokenUiComponent" /> diff --git a/app/etc/di.xml b/app/etc/di.xml index 157da1a49b470dc916fd9e43bc1648c0a4a725d2..a44eff21dc86823b8fd132bf1e75adec54fdc767 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1206,7 +1206,6 @@ <arguments> <argument name="mappers" xsi:type="array"> <item name="mapper" xsi:type="object">Magento\Framework\EntityManager\Mapper</item> - <item name="customAttributesMapper" xsi:type="object">Magento\Framework\EntityManager\CustomAttributesMapper</item> </argument> </arguments> </type> diff --git a/composer.json b/composer.json index a5f4b8c427ffffcbf85d9be78bcd89bf0928625a..fed4465140724cef3c24cd7facdd2ec5ceeba5b6 100644 --- a/composer.json +++ b/composer.json @@ -31,9 +31,9 @@ "zendframework/zend-serializer": "~2.4.6", "zendframework/zend-log": "~2.4.6", "zendframework/zend-http": "~2.4.6", - "magento/zendframework1": "1.12.16", + "magento/zendframework1": "~1.12.16", "colinmollenhour/credis": "1.6", - "colinmollenhour/php-redis-session-abstract": "1.1", + "colinmollenhour/php-redis-session-abstract": "1.2", "colinmollenhour/cache-backend-redis": "1.9", "colinmollenhour/cache-backend-file": "1.4", "composer/composer": "1.0.0-beta1", @@ -151,6 +151,7 @@ "magento/module-rss": "100.2.0-dev", "magento/module-rule": "100.2.0-dev", "magento/module-sales": "100.2.0-dev", + "magento/module-sales-inventory": "100.0.0-dev", "magento/module-sales-rule": "100.2.0-dev", "magento/module-sales-sequence": "100.2.0-dev", "magento/module-sample-data": "100.2.0-dev", diff --git a/composer.lock b/composer.lock index d842cdec858b705c27295b5b1b1d6e89ae5a6b19..7feb568d766d012e009c59a50226f3ea8bd7b74a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "1c5d7ca4fa68bea1ab7490cb8ac62342", - "content-hash": "ecfa548096f500a6a98a5fc41c21e53b", + "hash": "c23e80be1cc71ab108ce5ac19b3fe509", + "content-hash": "5b9734c1bdbda68cf20507525cafa0f2", "packages": [ { "name": "braintree/braintree_php", @@ -167,21 +167,21 @@ }, { "name": "colinmollenhour/php-redis-session-abstract", - "version": "v1.1", + "version": "v1.2", "source": { "type": "git", "url": "https://github.com/colinmollenhour/php-redis-session-abstract.git", - "reference": "95330b7f29663dab81f53d1a438e4d927b6c5f66" + "reference": "2b552c9bbe06967329dd41e1bd3e0aed02313ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/95330b7f29663dab81f53d1a438e4d927b6c5f66", - "reference": "95330b7f29663dab81f53d1a438e4d927b6c5f66", + "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/2b552c9bbe06967329dd41e1bd3e0aed02313ddb", + "reference": "2b552c9bbe06967329dd41e1bd3e0aed02313ddb", "shasum": "" }, "require": { "colinmollenhour/credis": "1.6", - "magento/zendframework1": "1.12.16", + "magento/zendframework1": "~1.12.0", "php": "~5.5.0|~5.6.0|~7.0.0" }, "type": "library", @@ -201,7 +201,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2016-02-03 18:13:49" + "time": "2016-08-04 18:05:51" }, { "name": "composer/composer", @@ -280,16 +280,16 @@ }, { "name": "composer/semver", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "03c9de5aa25e7672c4ad251eeaba0c47a06c8b98" + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/03c9de5aa25e7672c4ad251eeaba0c47a06c8b98", - "reference": "03c9de5aa25e7672c4ad251eeaba0c47a06c8b98", + "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", "shasum": "" }, "require": { @@ -338,20 +338,20 @@ "validation", "versioning" ], - "time": "2016-06-02 09:04:51" + "time": "2016-08-30 16:08:34" }, { "name": "composer/spdx-licenses", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88" + "reference": "96c6a07b05b716e89a44529d060bc7f5c263cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/88c26372b1afac36d8db601cdf04ad8716f53d88", - "reference": "88c26372b1afac36d8db601cdf04ad8716f53d88", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/96c6a07b05b716e89a44529d060bc7f5c263cb13", + "reference": "96c6a07b05b716e89a44529d060bc7f5c263cb13", "shasum": "" }, "require": { @@ -399,7 +399,7 @@ "spdx", "validator" ], - "time": "2016-05-04 12:27:30" + "time": "2016-09-28 07:17:45" }, { "name": "justinrainbow/json-schema", @@ -633,16 +633,16 @@ }, { "name": "magento/zendframework1", - "version": "1.12.16", + "version": "1.12.16-patch1", "source": { "type": "git", "url": "https://github.com/magento/zf1.git", - "reference": "c9d607bfd9454bc18b9deff737ccd5d044e2ab10" + "reference": "b4502f38f88ff2bc22a906c108cc3da18dcce12f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/zf1/zipball/c9d607bfd9454bc18b9deff737ccd5d044e2ab10", - "reference": "c9d607bfd9454bc18b9deff737ccd5d044e2ab10", + "url": "https://api.github.com/repos/magento/zf1/zipball/b4502f38f88ff2bc22a906c108cc3da18dcce12f", + "reference": "b4502f38f88ff2bc22a906c108cc3da18dcce12f", "shasum": "" }, "require": { @@ -676,7 +676,7 @@ "ZF1", "framework" ], - "time": "2015-10-29 14:34:55" + "time": "2016-09-14 12:04:33" }, { "name": "monolog/monolog", @@ -1014,22 +1014,30 @@ }, { "name": "psr/log", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + "reference": "5277094ed527a1c4477177d102fe4c53551953e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0", + "reference": "5277094ed527a1c4477177d102fe4c53551953e0", "shasum": "" }, + "require": { + "php": ">=5.3.0" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "psr-0": { - "Psr\\Log\\": "" + "psr-4": { + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1043,12 +1051,13 @@ } ], "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], - "time": "2012-12-21 11:40:51" + "time": "2016-09-19 16:02:08" }, { "name": "ramsey/uuid", @@ -1180,16 +1189,16 @@ }, { "name": "seld/jsonlint", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "66834d3e3566bb5798db7294619388786ae99394" + "reference": "e827b5254d3e58c736ea2c5616710983d80b0b70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/66834d3e3566bb5798db7294619388786ae99394", - "reference": "66834d3e3566bb5798db7294619388786ae99394", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e827b5254d3e58c736ea2c5616710983d80b0b70", + "reference": "e827b5254d3e58c736ea2c5616710983d80b0b70", "shasum": "" }, "require": { @@ -1222,7 +1231,7 @@ "parser", "validator" ], - "time": "2015-11-21 02:21:41" + "time": "2016-09-14 15:17:56" }, { "name": "seld/phar-utils", @@ -1381,7 +1390,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.9", + "version": "v2.8.11", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -1441,16 +1450,16 @@ }, { "name": "symfony/filesystem", - "version": "v2.8.9", + "version": "v2.8.11", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "ab4c3f085c8f5a56536845bf985c4cef30bf75fd" + "reference": "44b499521defddf2eae17a18c811bbdae4f98bdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ab4c3f085c8f5a56536845bf985c4cef30bf75fd", - "reference": "ab4c3f085c8f5a56536845bf985c4cef30bf75fd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/44b499521defddf2eae17a18c811bbdae4f98bdf", + "reference": "44b499521defddf2eae17a18c811bbdae4f98bdf", "shasum": "" }, "require": { @@ -1486,20 +1495,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2016-07-20 05:41:28" + "time": "2016-09-06 10:55:00" }, { "name": "symfony/finder", - "version": "v3.1.3", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8201978de88a9fa0923e18601bb17f1df9c721e7" + "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8201978de88a9fa0923e18601bb17f1df9c721e7", - "reference": "8201978de88a9fa0923e18601bb17f1df9c721e7", + "url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577", + "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577", "shasum": "" }, "require": { @@ -1535,20 +1544,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-08-26 12:04:02" }, { "name": "symfony/process", - "version": "v2.8.9", + "version": "v2.8.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d20332e43e8774ff8870b394f3dd6020cc7f8e0c" + "reference": "05a03ed27073638658cab9405d99a67dd1014987" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d20332e43e8774ff8870b394f3dd6020cc7f8e0c", - "reference": "d20332e43e8774ff8870b394f3dd6020cc7f8e0c", + "url": "https://api.github.com/repos/symfony/process/zipball/05a03ed27073638658cab9405d99a67dd1014987", + "reference": "05a03ed27073638658cab9405d99a67dd1014987", "shasum": "" }, "require": { @@ -1584,7 +1593,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-07-28 11:13:19" + "time": "2016-09-06 10:55:00" }, { "name": "tedivm/jshrink", @@ -3233,16 +3242,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v1.12.0", + "version": "v1.12.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "ddac737e1c06a310a0bb4b3da755a094a31a916a" + "reference": "baa7112bef3b86c65fcfaae9a7a50436e3902b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ddac737e1c06a310a0bb4b3da755a094a31a916a", - "reference": "ddac737e1c06a310a0bb4b3da755a094a31a916a", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/baa7112bef3b86c65fcfaae9a7a50436e3902b41", + "reference": "baa7112bef3b86c65fcfaae9a7a50436e3902b41", "shasum": "" }, "require": { @@ -3287,7 +3296,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2016-08-17 00:17:27" + "time": "2016-09-27 07:57:59" }, { "name": "lusitanian/oauth", @@ -4320,16 +4329,16 @@ }, { "name": "symfony/config", - "version": "v2.8.9", + "version": "v2.8.11", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4275ef5b59f18959df0eee3991e9ca0cc208ffd4" + "reference": "005bf10c156335ede2e89fb9a9ee10a0b742bc84" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4275ef5b59f18959df0eee3991e9ca0cc208ffd4", - "reference": "4275ef5b59f18959df0eee3991e9ca0cc208ffd4", + "url": "https://api.github.com/repos/symfony/config/zipball/005bf10c156335ede2e89fb9a9ee10a0b742bc84", + "reference": "005bf10c156335ede2e89fb9a9ee10a0b742bc84", "shasum": "" }, "require": { @@ -4369,20 +4378,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2016-07-26 08:02:44" + "time": "2016-08-16 14:56:08" }, { "name": "symfony/dependency-injection", - "version": "v2.8.9", + "version": "v2.8.11", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12" + "reference": "0a732a9cafc30e54077967da4d019e1d618a8cb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f2b5a00d176f6a201dc430375c0ef37706ea3d12", - "reference": "f2b5a00d176f6a201dc430375c0ef37706ea3d12", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0a732a9cafc30e54077967da4d019e1d618a8cb9", + "reference": "0a732a9cafc30e54077967da4d019e1d618a8cb9", "shasum": "" }, "require": { @@ -4432,11 +4441,11 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2016-07-30 07:20:35" + "time": "2016-09-06 23:19:39" }, { "name": "symfony/stopwatch", - "version": "v3.1.3", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -4485,16 +4494,16 @@ }, { "name": "symfony/yaml", - "version": "v2.8.9", + "version": "v2.8.11", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0ceab136f43ed9d3e97b3eea32a7855dc50c121d" + "reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0ceab136f43ed9d3e97b3eea32a7855dc50c121d", - "reference": "0ceab136f43ed9d3e97b3eea32a7855dc50c121d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e7540734bad981fe59f8ef14b6fc194ae9df8d9c", + "reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c", "shasum": "" }, "require": { @@ -4530,7 +4539,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-07-17 09:06:15" + "time": "2016-09-02 01:57:56" }, { "name": "theseer/fdomdocument", diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php index 5eeb7ddb61fa05657b08dda4711e82938e8778ce..0ad53eeeb90d9dab3a8877624429efefe314aefd 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php @@ -51,6 +51,7 @@ class ApiDataFixture */ public function startTest(\PHPUnit_Framework_TestCase $test) { + \Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); /** Apply method level fixtures if thy are available, apply class level fixtures otherwise */ $this->_applyFixtures($this->_getFixtures('method', $test) ?: $this->_getFixtures('class', $test)); } @@ -61,6 +62,9 @@ class ApiDataFixture public function endTest() { $this->_revertFixtures(); + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager->get(\Magento\Eav\Model\Entity\AttributeCache::class)->clear(); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php index 1bee8cdfa17804a7eb91db20d4400717aca50080..1279057089918c2154a42731799063dffc91f329 100755 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php @@ -31,6 +31,9 @@ class OrderCreateTest extends WebapiAbstract $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ protected function prepareOrder() { /** @var \Magento\Sales\Model\Order $orderBuilder */ @@ -41,6 +44,8 @@ class OrderCreateTest extends WebapiAbstract $orderPaymentFactory = $this->objectManager->get(\Magento\Sales\Model\Order\PaymentFactory::class); /** @var \Magento\Sales\Model\Order\AddressRepository $orderAddressRepository */ $orderAddressRepository = $this->objectManager->get(\Magento\Sales\Model\Order\AddressRepository::class); + /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); $order = $orderFactory->create( ['data' => $this->getDataStructure(\Magento\Sales\Api\Data\OrderInterface::class)] @@ -68,6 +73,32 @@ class OrderCreateTest extends WebapiAbstract $order->setCustomerEmail($email); $order->setBaseGrandTotal(100); $order->setGrandTotal(100); + $order->setShippingDescription('Flat Rate - Fixed'); + $order->setIsVirtual(0); + $order->setStoreId($storeManager->getDefaultStoreView()->getId()); + $order->setBaseDiscountAmount(0); + $order->setBaseShippingAmount(5); + $order->setBaseShippingTaxAmount(0); + $order->setBaseSubtotal(100); + $order->setBaseTaxAmount(0); + $order->setBaseToGlobalRate(1); + $order->setBaseToOrderRate(1); + $order->setDiscountAmount(0); + $order->setShippingAmount(0); + $order->setShippingTaxAmount(0); + $order->setStoreToOrderRate(0); + $order->setBaseToOrderRate(0); + $order->setSubtotal(100); + $order->setTaxAmount(0); + $order->setTotalQtyOrdered(1); + $order->setCustomerIsGuest(1); + $order->setCustomerNoteNotify(0); + $order->setCustomerGroupId(0); + $order->setBaseSubtotalInclTax(100); + $order->setWeight(1); + $order->setBaseCurrencyCode('USD'); + $order->setShippingInclTax(5); + $order->setBaseShippingInclTax(5); $this->addProductOption($orderItem); @@ -82,12 +113,39 @@ class OrderCreateTest extends WebapiAbstract $orderAddressBilling->setFirstname('First Name'); $orderAddressBilling->setTelephone('+00(000)-123-45-57'); $orderAddressBilling->setStreet(['Street']); - $orderAddressBilling->setCountryId(1); + $orderAddressBilling->setCountryId('US'); + $orderAddressBilling->setRegion('California'); $orderAddressBilling->setAddressType('billing'); + $orderAddressBilling->setRegionId(12); + + $orderAddressShipping = $orderAddressRepository->create(); + $orderAddressShipping->setCity('City2'); + $orderAddressShipping->setPostcode('12345'); + $orderAddressShipping->setLastname('Last Name2'); + $orderAddressShipping->setFirstname('First Name2'); + $orderAddressShipping->setTelephone('+00(000)-123-45-57'); + $orderAddressShipping->setStreet(['Street']); + $orderAddressShipping->setCountryId('US'); + $orderAddressShipping->setRegion('California'); + $orderAddressShipping->setAddressType('shipping'); + $orderAddressShipping->setRegionId(12); $orderData = $order->getData(); $orderData['billing_address'] = $orderAddressBilling->getData(); $orderData['billing_address']['street'] = ['Street']; + $address = $orderAddressShipping->getData(); + $address['street'] = ['Street']; + $orderData['extension_attributes']['shipping_assignments'] = + [ + [ + 'shipping' => [ + 'address' => $address, + 'method' => 'Flat Rate - Fixed' + ], + 'items' => [$orderItem->getData()], + 'stock_id' => null, + ] + ]; return $orderData; } @@ -172,5 +230,8 @@ class OrderCreateTest extends WebapiAbstract $this->assertTrue((bool)$model->getId()); $this->assertEquals($order['base_grand_total'], $model->getBaseGrandTotal()); $this->assertEquals($order['grand_total'], $model->getGrandTotal()); + $this->assertNotNull($model->getShippingAddress()); + $this->assertTrue((bool)$model->getShippingAddress()->getId()); + $this->assertEquals('Flat Rate - Fixed', $model->getShippingMethod()); } } diff --git a/dev/tests/api-functional/testsuite/Magento/SalesInventory/Api/Service/V1/ReturnItemsAfterRefundOrderTest.php b/dev/tests/api-functional/testsuite/Magento/SalesInventory/Api/Service/V1/ReturnItemsAfterRefundOrderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..68ecebec27c0b0812d76eb7be8c2ac063aff54ef --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/SalesInventory/Api/Service/V1/ReturnItemsAfterRefundOrderTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\SalesInventory\Api\Service\V1; + +/** + * API test for return items to stock + */ +class ReturnItemsAfterRefundOrderTest extends \Magento\TestFramework\TestCase\WebapiAbstract +{ + const SERVICE_REFUND_ORDER_NAME = 'salesRefundOrderV1'; + const SERVICE_STOCK_ITEMS_NAME = 'stockItems'; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @dataProvider dataProvider + * @magentoApiDataFixture Magento/Sales/_files/order_with_shipping_and_invoice.php + */ + public function testRefundWithReturnItemsToStock($qtyRefund) + { + $productSku = 'simple'; + /** @var \Magento\Sales\Model\Order $existingOrder */ + $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class) + ->loadByIncrementId('100000001'); + $orderItems = $existingOrder->getItems(); + $orderItem = array_shift($orderItems); + $expectedItems = [['order_item_id' => $orderItem->getItemId(), 'qty' => $qtyRefund]]; + $qtyBeforeRefund = $this->getQtyInStockBySku($productSku); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $existingOrder->getEntityId() . '/refund', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_REFUND_ORDER_NAME, + 'serviceVersion' => 'V1', + 'operation' => self::SERVICE_REFUND_ORDER_NAME . 'execute', + ] + ]; + + $this->_webApiCall( + $serviceInfo, + [ + 'orderId' => $existingOrder->getEntityId(), + 'items' => $expectedItems, + 'arguments' => [ + 'extension_attributes' => [ + 'return_to_stock_items' => [ + (int)$orderItem->getItemId() + ], + ], + ], + ] + ); + + $qtyAfterRefund = $this->getQtyInStockBySku($productSku); + + try { + $this->assertEquals( + $qtyBeforeRefund + $expectedItems[0]['qty'], + $qtyAfterRefund, + 'Failed asserting qty of returned items incorrect.' + ); + + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->fail('Failed asserting that Creditmemo was created'); + } + } + + /** + * @return array + */ + public function dataProvider() + { + return [ + 'refundAllOrderItems' => [2], + 'refundPartition' => [1], + ]; + } + + /** + * @param string $sku + * @return int + */ + private function getQtyInStockBySku($sku) + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/' . self::SERVICE_STOCK_ITEMS_NAME . "/$sku", + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => 'catalogInventoryStockRegistryV1', + 'serviceVersion' => 'V1', + 'operation' => 'catalogInventoryStockRegistryV1GetStockItemBySku', + ], + ]; + $arguments = ['productSku' => $sku]; + $apiResult = $this->_webApiCall($serviceInfo, $arguments); + return $apiResult['qty']; + } +} diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php new file mode 100644 index 0000000000000000000000000000000000000000..ceb84eb46acc97c590440e2f7f5e6031ddc969b4 --- /dev/null +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Cli/Setup.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Mtf\Util\Command\Cli; + +use Magento\Mtf\Util\Command\Cli; + +/** + * Setup Magento for tests executions. + */ +class Setup extends Cli +{ + /** + * Parameter for uninstall Magento command. + */ + const PARAM_SETUP_UNINSTALL = 'setup:uninstall'; + + /** + * Options for uninstall Magento command. + * + * @var array + */ + private $options = ['-n']; + + /** + * Uninstall Magento. + * + * @return void + */ + public function uninstall() + { + parent::execute(Setup::PARAM_SETUP_UNINSTALL, $this->options); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml index c949d827b4879599685bca266e12edfd2eb3a384..d4cf5dab742aa37684e8b5ee884bd6164c88426f 100644 --- a/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Authorizenet/Test/TestCase/OnePageCheckoutTest.xml @@ -6,12 +6,12 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> - <testCase name="Magento\Checkout\Test\TestCase\OnePageCheckoutTest" summary="One page check out with Authorize.Net payment method."> - <variation name="OnePageCheckoutAuthorizenetTestVariation1" summary="Check Out as a Guest with Authorize.Net and Offline Shipping method" ticketId="MAGETWO-12832"> + <testCase name="Magento\Checkout\Test\TestCase\OnePageCheckoutTest" summary="One page check out with Authorize.Net Direct Post payment method."> + <variation name="OnePageCheckoutAuthorizenetTestVariation1" summary="CheckOut with Authorize.Net Direct Post" ticketId="MAGETWO-59170"> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="customer/dataset" xsi:type="string">default</data> - <data name="shippingAddress/dataset" xsi:type="string">US_address_1</data> - <data name="checkoutMethod" xsi:type="string">guest</data> + <data name="checkoutMethod" xsi:type="string">login</data> + <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data> <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> <data name="shipping/shipping_method" xsi:type="string">Fixed</data> <data name="prices" xsi:type="array"> @@ -26,7 +26,7 @@ <item name="isClosed" xsi:type="string">No</item> <item name="transactionType" xsi:type="string">Authorization</item> </data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/Edit/Form/StoreForm.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/Edit/Form/StoreForm.php index 3aa435226553555a927cf7695060e5392a72a65c..9f20ae953c2117bae7e5c9765bc36d5d4e43d873 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/Edit/Form/StoreForm.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/Edit/Form/StoreForm.php @@ -22,6 +22,13 @@ class StoreForm extends Form */ protected $store = '//option[contains(.,"%s")]'; + /** + * Store dropdown selector + * + * @var string + */ + protected $storeGroupId = '//select[@id="store_group_id"]'; + /** * Check that Store visible in Store dropdown * @@ -32,4 +39,16 @@ class StoreForm extends Form { return $this->_rootElement->find(sprintf($this->store, $name), Locator::SELECTOR_XPATH)->isVisible(); } + + /** + * Select store view by name from dropdown + * + * @param string $name + * @return bool + */ + public function selectStore($name) + { + return $this->_rootElement->find($this->storeGroupId, Locator::SELECTOR_XPATH, 'optgroupselect') + ->setValue($name); + } } diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.php index 6601bd44892c93e30573c5342e483eb232573e70..dda155f7fd66bd33a5532ab4a36c937a1d10178b 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.php @@ -38,6 +38,7 @@ class BraintreeSettlementReportTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.xml index 183dc6ac906074fb6dc918e765d648ea437fd05b..e80f6071ae5d1d33b37bb7c26c7389ed08aa8962 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/BraintreeSettlementReportTest.xml @@ -22,7 +22,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.php index a0bf482705f552318a7cdd09951aaff14eaf1cb2..c9d0904c85d675d7a4aa360ac6a2f8c00c6b0862 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.php @@ -33,6 +33,7 @@ class CheckoutWithBraintreePaypalCartTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.xml index 09b33bfa79958668ceef14a20e48cf0ecce2646e..9e1090d6a22f91102be14f0f4319921651f01841 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalCartTest.xml @@ -20,7 +20,7 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_skip_order_review</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -42,7 +42,7 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_sale, braintree_paypal_skip_order_review</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.php index f0f0291b33e20428765c110dbb23600ba463bdde..9ee6bfb7c4e29b84043fd3b116a09153bfab4233 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.php @@ -33,6 +33,7 @@ class CheckoutWithBraintreePaypalMinicartTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.xml index b426e7578729dd3b0ba514440b7b425b373ae20b..b41ea91e4598255a67e3e44180895873358a9b1a 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CheckoutWithBraintreePaypalMinicartTest.xml @@ -20,7 +20,7 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_skip_order_review</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -42,7 +42,7 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_sale, braintree_paypal_skip_order_review</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.php index 6554845d5ff81af96b887a9dbcb8e05761b21463..9e6cf0991db4e5fb2c3dd2c45de7d365e67395f6 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.php @@ -25,6 +25,7 @@ class CreateOnlineCreditMemoBraintreePaypalTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.xml index 56c33af7a82b986b23372c9cbf0f2e8e915876a3..53847e55fd9f91e29fbeeb6c71a0402a1fb6a6f6 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineCreditMemoBraintreePaypalTest.xml @@ -22,9 +22,8 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_sale, braintree_paypal_skip_order_review</data> <data name="paymentAction" xsi:type="string">sale</data> - <data name="orderButtonsAvailable" xsi:type="string">Back, Send Email, Credit Memo, Hold, Ship, Reorder</data> <data name="data/items_data/0/qty" xsi:type="string">-</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCreditMemoTab" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCommentsHistory" /> @@ -42,12 +41,11 @@ <data name="shipping/shipping_method" xsi:type="string">Fixed</data> <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_skip_order_review</data> - <data name="orderButtonsAvailable" xsi:type="string">Back, Send Email, Credit Memo, Hold, Ship, Reorder</data> <data name="data/items_data/0/qty" xsi:type="string">2</data> <data name="refundData/items_data/0/qty" xsi:type="string">1</data> <data name="order/dataset" xsi:type="string">default</data> <data name="isCreditMemoPartial" xsi:type="string">Yes</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCreditMemoTab" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCommentsHistory" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineInvoiceEntityTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineInvoiceEntityTest.xml index 702a732e937eabedff8f6525e651fe4c16de942c..d9f382e4b650e5ce48955e264c67b36c893dbb84 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineInvoiceEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOnlineInvoiceEntityTest.xml @@ -29,7 +29,7 @@ <data name="data/items_data/0/qty" xsi:type="string">-</data> <data name="data/form_data/do_shipment" xsi:type="string">No</data> <data name="data/form_data/comment_text" xsi:type="string">comments</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" /> @@ -58,7 +58,7 @@ <data name="data/items_data/0/qty" xsi:type="string">1</data> <data name="data/form_data/do_shipment" xsi:type="string">No</data> <data name="data/form_data/comment_text" xsi:type="string">comments</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml index 80596fb577b970d41e01e8e98d26c86a28262b70..9614923691c6c097702c3a222bfc52edfcd2489a 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\CreateOrderBackendTest" summary="Checkout with Braintree Credit Card from Admin"> <variation name="CreateOrderBackendTestBraintreeVariation1" summary="Checkout with Braintree Credit Card from Admin (Payment Action = Authorize Only)" ticketId="MAGETWO-46294"> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="products/1" xsi:type="string">configurableProduct::with_one_option</data> <data name="products/2" xsi:type="string">bundleProduct::bundle_fixed_100_dollar_product</data> @@ -32,11 +32,10 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" /> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> </variation> <variation name="CreateOrderBackendTestBraintreeVariation2" summary="Checkout with Braintree Credit Card from Admin (Payment Action = Authorize and Capture)" ticketId="MAGETWO-38316"> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, test_type:3rd_party_test, severity:S0</data> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="products/1" xsi:type="string">configurableProduct::with_one_option</data> <data name="products/2" xsi:type="string">bundleProduct::bundle_fixed_100_dollar_product</data> @@ -63,7 +62,6 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" /> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml index 4f05723609c2a8305f68a6893787684ae2e3d9df..db6cd3572eaa2acec5ded94997b1951c744e6652 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Vault\Test\TestCase\CreateVaultOrderBackendTest" summary="Create Order from Admin via Braintree with saving Credit Cards"> <variation name="CreateVaultOrderBackendTestBraintreeVariation1" summary="Checkout with Braintree Credit Card from Admin (Payment Action = Authorize Only)" ticketId="MAGETWO-47137, MAGETWO-47139"> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="customer/dataset" xsi:type="string">default</data> <data name="billingAddress/dataset" xsi:type="string">US_address_1_without_email</data> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php index 7048984886582f7e74ffaa6d5d0597ee4ae5b018..062de338d8921e15b786de0fd4b656e824354d80 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePayPalBraintreeTest.php @@ -29,6 +29,7 @@ class InvoicePayPalBraintreeTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml index bae626a427818b115d897d06dd33b4a894da1cba..352e0ed684919a43df66b429d5b8250d90cae351 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/InvoicePaypalBraintreeTest.xml @@ -26,7 +26,7 @@ <data name="data/items_data/0/qty" xsi:type="string">-</data> <data name="data/form_data/do_shipment" xsi:type="string">No</data> <data name="data/form_data/comment_text" xsi:type="string">comments</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" /> @@ -51,7 +51,7 @@ <data name="data/items_data/0/qty" xsi:type="string">1</data> <data name="data/form_data/do_shipment" xsi:type="string">No</data> <data name="data/form_data/comment_text" xsi:type="string">comments</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.php index 41f637d52941d463223e1706e1175939e8c817ee..68d36f3c754cf6ff5bca786cb314fd89e51f9c5d 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.php @@ -8,17 +8,25 @@ namespace Magento\Braintree\Test\TestCase; use Magento\Mtf\TestCase\Scenario; /** - * Class OnePageCheckoutAcceptPaymentTest + * Preconditions: + * 1. Order is placed with Braintree Credit Card from Storefront with Advanced Fraud Protection. * - * This scenario places order via Braintree payment with - * enabled Advanced Fraud protection and accept payment for placed order - * to future processing + * Steps: + * 1. Log in to Admin. + * 2. Go to Sales > Orders page. + * 3. Open the placed order. + * 4. Click Accept button. + * 5. Perform assertions. + * + * @group Braintree + * @ZephyrId MAGETWO-56023 */ class OnePageCheckoutAcceptPaymentTest extends Scenario { /* tags */ const MVP = 'yes'; const TEST_TYPE = 'acceptance_test, 3rd_party_test'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.xml index f42054b4a07325512db19b497627d0099dbc7a3a..841145d7a5fdfaa92f1a82ac00c9bf51f0601643 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutAcceptPaymentTest.xml @@ -21,7 +21,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree,braintree_fraudprotection</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S2</data> <constraint name="Magento\Sales\Test\Constraint\AssertAcceptPaymentSuccessMessagePresent" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertAcceptPaymentMessageInCommentsHistory" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.php index 69fd4a8323eca15728d9c6f5b65c91a4929dc562..143ecb5a65d7d4a6af6a9b00d4b15175230cd0f4 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.php @@ -8,16 +8,25 @@ namespace Magento\Braintree\Test\TestCase; use Magento\Mtf\TestCase\Scenario; /** - * Class OnePageCheckoutDenyPaymentTest + * Preconditions: + * 1. Order is placed with Braintree Credit Card from Storefront with Advanced Fraud Protection. * - * This scenario places order via Braintree payment with - * enabled Advanced Fraud protection and deny payment for placed order + * Steps: + * 1. Log in to Admin. + * 2. Go to Sales > Orders page. + * 3. Open the placed order. + * 4. Click Deny button. + * 5. Perform assertions. + * + * @group Braintree + * @ZephyrId MAGETWO-56024 */ class OnePageCheckoutDenyPaymentTest extends Scenario { /* tags */ const MVP = 'yes'; const TEST_TYPE = 'acceptance_test, 3rd_party_test'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.xml index 45476abfdfd12c18dc8a7b36782bc0b8f30ed3c0..95d07079ef36c9ae66672ec8cc465325273ecda4 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutDenyPaymentTest.xml @@ -21,7 +21,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree,braintree_fraudprotection</data> <data name="status" xsi:type="string">Canceled</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S2</data> <constraint name="Magento\Sales\Test\Constraint\AssertDenyPaymentSuccessMessagePresent" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertDenyPaymentMessageInCommentsHistory" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml index a1ceaa53da045345a1e4a7a5a51089a5623a85b8..1e9c539c8c0d379d6e5b69929e9270b21a8cc593 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutTest.xml @@ -25,7 +25,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree_3dsecure</data> <data name="configData" xsi:type="string">braintree, braintree_3d_secure_not_triggered_due_threshold</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -48,7 +48,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree_3dsecure</data> <data name="configData" xsi:type="string">braintree, braintree_3d_secure_uk</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -71,13 +71,13 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test, test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" /> </variation> - <variation name="OnePageCheckoutBraintreeTestVariation4" summary="Checkout with Braintree for payment action Authorize and Capture" ticketId="MAGETWO-38420"> + <variation name="OnePageCheckoutBraintreeTestVariation4" summary="Checkout with Braintree for payment action Authorize and Capture" ticketId="MAGETWO-59403"> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="products/1" xsi:type="string">configurableProduct::with_one_option</data> <data name="products/2" xsi:type="string">bundleProduct::bundle_fixed_100_dollar_product</data> @@ -98,7 +98,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree, braintree_sale</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.php index 32599aa090651c76f7d9d2663309ea3a219f79f6..e4b7d82094db5a615b3df3000f18fbd9a1f41b6d 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.php @@ -39,6 +39,7 @@ class OnePageCheckoutWith3dSecureTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.xml index 4cb5b1702dd3baac27c41b3c7367456299e8ef8e..295a9dcd5c5332dce7d6e5fad9dfcdc6f5352596 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWith3dSecureTest.xml @@ -31,7 +31,7 @@ <data name="secure3d/dataset" xsi:type="string">secure3d_braintree</data> <data name="configData" xsi:type="string">braintree, braintree_3d_secure</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.php index 9cf0e9c90c1215649e50e0a708bcae5db371d9fd..a984f666c435680c68b97d0481b8b86a3f487b0e 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.php @@ -37,6 +37,7 @@ class OnePageCheckoutWithBraintreePaypalTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.xml index 8f2177209f37b7f83d2b5ad97e2599ed7f7ff67c..7e38e6f0303b64e55ce6e126a2653f0ce7420df7 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithBraintreePaypalTest.xml @@ -21,7 +21,7 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_skip_order_review</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> @@ -44,7 +44,7 @@ <data name="payment/method" xsi:type="string">braintree_paypal</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_sale, braintree_paypal_skip_order_review</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml index 89d67936ab0c610cd67054d428c39cd8da341b53..23fc729edb99f9645f7587d8e350464c81083fcd 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml @@ -25,12 +25,10 @@ <data name="creditCardSave" xsi:type="string">Yes</data> <data name="configData" xsi:type="string">braintree, braintree_use_vault</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> - <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Email, Hold, Invoice, Ship, Reorder, Edit</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml index 88ab2ef65c9a0e75d2eafff8c53fe898d9504efe..5a99c4a89a313392cdd596891712b1d2d4bbaec3 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml @@ -23,7 +23,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree, braintree_use_vault</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.php index f6813b74e40d7b09edf48369f6dc79ca28fefa7d..8845f2a78629da6d2c3994b1e691cce117efa8bc 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.php @@ -43,6 +43,7 @@ class SaveUseDeleteVaultForPaypalBraintreeTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.xml index 6e214d4c205aacc72fa8b93cbd4c082d2d02fa96..c7a5182a1dfb3d693dc03eb5c77d9d2c26622a58 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/SaveUseDeleteVaultForPaypalBraintreeTest.xml @@ -23,7 +23,7 @@ <data name="creditCardSave" xsi:type="string">Yes</data> <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_use_vault</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Vault\Test\Constraint\AssertStoredPaymentDeletedMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultOnCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultOnCheckoutTest.xml index 00eb9c4802db6216bd1dbc0202231612166b306c..35d55784b676236d808cf0f54fc969fc65193b0b 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultOnCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultOnCheckoutTest.xml @@ -22,7 +22,7 @@ <data name="creditCardSave" xsi:type="string">Yes</data> <data name="configData" xsi:type="string">braintree, braintree_use_vault</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Email, Hold, Invoice, Ship, Reorder, Edit</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.php index 13b8240d4c49ebe4b047e59f6bfdb63102fbcf47..d2723a300285df0b560ece526e463b495f201d8f 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.php @@ -43,6 +43,7 @@ class UseVaultWith3dSecureOnCheckoutTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.xml index 0f00ff93612b8b1ef7792971b38846efbe3d2b43..a5aa7645148e158ad62a174bc26d08abfd037192 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/UseVaultWith3dSecureOnCheckoutTest.xml @@ -27,7 +27,7 @@ <data name="creditCardSave" xsi:type="string">Yes</data> <data name="configData" xsi:type="string">braintree, braintree_use_vault, braintree_3d_secure</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Email, Hold, Invoice, Ship</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/di.xml index be2d771e47f5bfaf9c93ac9ac909d87ef723bff1..7bdfca2f713f20a9f41b4c19d9bb1e36f55a59c1 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/di.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/di.xml @@ -8,12 +8,12 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Braintree\Test\Constraint\Assert3dSecureInfoIsPresent"> <arguments> - <argument name="severity" xsi:type="string">high</argument> + <argument name="severity" xsi:type="string">S1</argument> </arguments> </type> <type name="Magento\Braintree\Test\Constraint\AssertTransactionIsPresentInSettlementReport"> <arguments> - <argument name="severity" xsi:type="string">high</argument> + <argument name="severity" xsi:type="string">S1</argument> </arguments> </type> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml index 63535106514f2c893b0f822daeb7350fd7d892a3..dff1a9fd71d865dd52fd6cdd0e05d3e3d000db94 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Category/Edit/CategoryForm.xml @@ -130,8 +130,8 @@ </design> <schedule_design_update> <class>\Magento\Ui\Test\Block\Adminhtml\Section</class> - <selector>//div[contains(@class,'admin__collapsible-block-wrapper')][descendant::input[@name='custom_design_to']]</selector> - <strategy>xpath</strategy> + <selector>[data-index="schedule_design_update"]</selector> + <strategy>css selector</strategy> <fields> <schedule_update_from> <input>datepicker</input> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Attribute.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Attribute.php deleted file mode 100644 index ca29475c03c90181f7caa0849e11a7639b023158..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Attribute.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action; - -use Magento\Mtf\Fixture\FixtureInterface; -use Magento\Mtf\Block\Form; -use Magento\Mtf\Client\Element\SimpleElement; - -/** - * Product attribute massaction edit page. - */ -class Attribute extends Form -{ - /** - * Fill the root form. - * - * @param FixtureInterface $fixture - * @param SimpleElement|null $element - * @return $this - */ - public function fill(FixtureInterface $fixture, SimpleElement $element = null) - { - $data = $fixture->getData(); - $fields = []; - foreach ($data as $name => $dataValue) { - $fields['toggle_' . $name] = 'Yes'; - $fields[$name] = $dataValue; - } - $mapping = $this->dataMapping($fields); - $this->_fill($mapping, $element); - - return $this; - } -} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Attribute.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Attribute.xml deleted file mode 100644 index 59b367e49bd66b8dce3cfb1e25dc4b46f9e2eff8..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Attribute.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<mapping strict="0"> - <wrapper>attributes</wrapper> - <fields> - <toggle_price> - <selector>[name='toggle_price']</selector> - <input>checkbox</input> - </toggle_price> - <price /> - </fields> -</mapping> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Tab/UpdateAttributeTab.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Tab/UpdateAttributeTab.php new file mode 100644 index 0000000000000000000000000000000000000000..d61c2001a2096ebaedd895a0c483d5a93a9ae8bd --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/Tab/UpdateAttributeTab.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action\Tab; + +use Magento\Mtf\Client\Element\SimpleElement; +use Magento\Mtf\Client\Locator; +use Magento\Backend\Test\Block\Widget\Tab; + +/** + * Tab on Product update attributes Form. + */ +class UpdateAttributeTab extends Tab +{ + /** + * Change checkbox. + * + * @var string + */ + private $changeCheckbox = [ + 'selector' => './/./ancestor::div[contains(@class,"control")]' + . '//input[@data-role="toggle-editability-all" or contains(@id, "toggle_")]', + 'strategy' => Locator::SELECTOR_XPATH, + 'input' => 'checkbox', + 'value' => 'Yes', + ]; + + /** + * Fill data into fields in the container. + * + * @param array $fields + * @param SimpleElement|null $contextElement + * @return $this + */ + public function setFieldsData(array $fields, SimpleElement $contextElement = null) + { + $context = ($contextElement === null) ? $this->_rootElement : $contextElement; + $mapping = $this->dataMapping($fields); + foreach ($mapping as $field) { + $this->_fill([$this->changeCheckbox], $context->find($field['selector'], $field['strategy'])); + $this->_fill([$field], $context); + } + + return $this; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/UpdateAttributeForm.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/UpdateAttributeForm.php new file mode 100644 index 0000000000000000000000000000000000000000..a264c491de43aa6fdfd22eb98793316e79996054 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/UpdateAttributeForm.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action; + +use Magento\Backend\Test\Block\Widget\FormTabs; + +/** + * Product update Attributes Form. + */ +class UpdateAttributeForm extends FormTabs +{ + // +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/UpdateAttributeForm.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/UpdateAttributeForm.xml new file mode 100644 index 0000000000000000000000000000000000000000..ba8effd797a3dc85e92601fd7037815ef02c91a9 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Action/UpdateAttributeForm.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tabs> + <product-details> + <class>Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action\Tab\UpdateAttributeTab</class> + <selector>#attributes_update_tabs_attributes</selector> + <fields> + <price> + <selector>#price</selector> + </price> + </fields> + </product-details> + <advanced-inventory> + <class>Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action\Tab\UpdateAttributeTab</class> + <selector>#attributes_update_tabs_inventory</selector> + <fields> + <stock_data> + <selector>#inventory_stock_availability</selector> + <input>select</input> + </stock_data> + </fields> + </advanced-inventory> +</tabs> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php index d5e81ad56fd3290ef906c8cb141da8e583757c24..db73f7cc87f177fb1eb05f0184b238cb82cd47f1 100755 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Test\Block\Adminhtml\Product; use Magento\Ui\Test\Block\Adminhtml\DataGrid; +use Magento\Mtf\Fixture\FixtureInterface; /** * Backend catalog product grid. @@ -72,12 +73,17 @@ class Grid extends DataGrid /** * Update attributes for selected items. * - * @param array $items [optional] + * @param array $items * @return void */ public function updateAttributes(array $items = []) { - $this->massaction($items, 'Update attributes'); + $products = []; + /** @var FixtureInterface $product */ + foreach ($items as $product) { + $products[] = ["sku" => $product->getSku()]; + } + $this->massaction($products, 'Update attributes'); } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php index 546b055b0045b29d6cfe216b1b0efdc7407ebc0a..4de51f3e2ca9b8d5089cb6818d380f0ac9465e46 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php @@ -86,6 +86,13 @@ class ListCompare extends Block */ protected $messageBlock = '#messages'; + /** + * Selector for confirm. + * + * @var string + */ + protected $confirmModal = '.confirm._show[data-role=modal]'; + /** * Get product info. * @@ -189,6 +196,13 @@ class ListCompare extends Block public function removeProduct($index = 1) { $this->_rootElement->find(sprintf($this->removeButton, $index), Locator::SELECTOR_XPATH)->click(); + $modalElement = $this->browser->find($this->confirmModal); + /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */ + $modal = $this->blockFactory->create( + \Magento\Ui\Test\Block\Adminhtml\Modal::class, + ['element' => $modalElement] + ); + $modal->acceptAlert(); } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php index 596dbd38664b3b097302e6f6f0e62d13fbe96017..ab65865af6534c2b2478490b7f08fd7f108496fe 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php @@ -32,6 +32,13 @@ class Sidebar extends ListCompare */ protected $clearAll = '#compare-clear-all'; + /** + * Selector for confirm. + * + * @var string + */ + protected $confirmModal = '.confirm._show[data-role=modal]'; + /** * Get compare products block content. * @@ -79,5 +86,12 @@ class Sidebar extends ListCompare } ); $this->_rootElement->find($this->clearAll)->click(); + $modalElement = $this->browser->find($this->confirmModal); + /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */ + $modal = $this->blockFactory->create( + \Magento\Ui\Test\Block\Adminhtml\Modal::class, + ['element' => $modalElement] + ); + $modal->acceptAlert(); } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php index 100930ad20aea7a40eff2fcf6b367c85db48a242..3d5e550560e857b8530037637b4f97f7763874e2 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/ProductList/TopToolbar.php @@ -28,7 +28,9 @@ class TopToolbar extends Block */ public function getSelectSortType() { - return $this->_rootElement->find("#sorter")->getElements('option[selected]')[0]->getText(); + $selectedOption = $this->_rootElement->find($this->sorter)->getElements('option[selected]')[0]->getText(); + preg_match('/\w+\s?\w+/', $selectedOption, $matches); + return $matches[0]; } /** @@ -38,7 +40,8 @@ class TopToolbar extends Block */ public function getSortType() { - $content = str_replace("\r", '', $this->_rootElement->find($this->sorter)->getText()); - return explode("\n", $content); + $content = $this->_rootElement->find($this->sorter)->getText(); + preg_match_all('/\w+\s?\w+/', $content, $matches); + return $matches[0]; } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertMassProductUpdateSuccessMessage.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertMassProductUpdateSuccessMessage.php index 1666244802ed2e81c5375c2d41b0df0dff0a0191..df326a9ca0023a3c3fbeb45a2c960e88d9fffc32 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertMassProductUpdateSuccessMessage.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertMassProductUpdateSuccessMessage.php @@ -23,13 +23,12 @@ class AssertMassProductUpdateSuccessMessage extends AbstractConstraint * Assert that after mass update successful message appears. * * @param CatalogProductIndex $productGrid - * @param array $products + * @param int $productsCount * @return void */ - public function processAssert(CatalogProductIndex $productGrid, $products = []) + public function processAssert(CatalogProductIndex $productGrid, $productsCount) { - $countProducts = count($products) ? count($products) : 1; - $expectedMessage = sprintf(self::SUCCESS_MESSAGE, $countProducts); + $expectedMessage = sprintf(self::SUCCESS_MESSAGE, $productsCount); $actualMessage = $productGrid->getMessagesBlock()->getSuccessMessage(); \PHPUnit_Framework_Assert::assertEquals( $expectedMessage, diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductsInStock.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductsInStock.php new file mode 100644 index 0000000000000000000000000000000000000000..f485076d5c018b97dfc67624d505bb617929b901 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductsInStock.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Constraint; + +use Magento\Catalog\Test\Page\Product\CatalogProductView; +use Magento\Mtf\Client\BrowserInterface; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert that all products are in stock. + */ +class AssertProductsInStock extends AbstractConstraint +{ + /** + * Assert that In Stock status is displayed for products. + * + * @param CatalogProductView $catalogProductView + * @param BrowserInterface $browser + * @param AssertProductInStock $assertProductInStock + * @param array $products + * @return void + */ + public function processAssert( + CatalogProductView $catalogProductView, + BrowserInterface $browser, + AssertProductInStock $assertProductInStock, + array $products + ) { + foreach ($products as $product) { + $assertProductInStock->processAssert($catalogProductView, $browser, $product); + } + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'In stock control is visible for each product.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductActionAttributeEdit.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductActionAttributeEdit.xml index e875bdc0817e9bcd97b4c933de90ede7498aec7a..1ff06177a5a20b716f1a24201a785c1ff12eecd6 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductActionAttributeEdit.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductActionAttributeEdit.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="CatalogProductActionAttributeEdit" area="Adminhtml" mca="catalog/product_action_attribute/edit" module="Magento_Catalog"> - <block name="attributesBlockForm" class="Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action\Attribute" locator="body" strategy="css selector" /> + <block name="attributesBlockForm" class="Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action\UpdateAttributeForm" locator="body" strategy="css selector" /> <block name="formPageActions" class="Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Action\FormPageActions" locator=".page-main-actions" strategy="css selector" /> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml index 30f870dea88f063a7278d7380f5d4fe24f508c9f..2fd8c04d1d80dfe643616ee7c550699da91aeece 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml @@ -67,7 +67,7 @@ <field name="name" xsi:type="string">Category%isolation%</field> <field name="url_key" xsi:type="string">category-%isolation%</field> <field name="parent_id" xsi:type="array"> - <item name="dataset" xsi:type="string">default_subcategory</item> + <item name="dataset" xsi:type="string">default</item> </field> <field name="is_active" xsi:type="string">Yes</field> <field name="include_in_menu" xsi:type="string">Yes</field> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml index 0b3452cc147824b89443c33ca86d29cbebcb7ae7..9ae1e2ebfacca405f8505496f546cf2182e2dd3f 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml @@ -23,5 +23,16 @@ <item name="value" xsi:type="number">0</item> </field> </dataset> + <dataset name="empty_product_mask_sku"> + <field name="catalog/fields_masks/sku" xsi:type="array"> + <item name="value" xsi:type="string"/> + </field> + </dataset> + <dataset name="empty_product_mask_sku_rollback"> + <field name="catalog/fields_masks/sku" xsi:type="array"> + <item name="value" xsi:type="string">{{name}}</item> + <item name="inherit" xsi:type="number">1</item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/TierPrice.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/TierPrice.xml index bdfb5deabe5c2828b947130284f0308353fa6bdc..412818cbe40e71634eb1e635d8dc8e1630913b49 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/TierPrice.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/TierPrice.xml @@ -66,5 +66,16 @@ </item> </field> </dataset> + + <dataset name="not_logged_in"> + <field name="0" xsi:type="array"> + <item name="price" xsi:type="string">90</item> + <item name="website" xsi:type="string">All Websites [USD]</item> + <item name="price_qty" xsi:type="string">2</item> + <item name="customer_group" xsi:type="array"> + <item name="dataset" xsi:type="string">NOT_LOGGED_IN</item> + </item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml index 7e6f913dda6e7fd88c0723ccbd5e899ccbeeaafe..22cdab45ac875c6a6987892898e3c8dda3382944 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/CreateCategoryEntityTest.xml @@ -39,8 +39,8 @@ <data name="category/data/layout_update_xml" xsi:type="string"><referenceContainer name="catalog.leftnav" remove="true"/></data> <data name="category/data/new_theme" xsi:type="string">Magento Luma</data> <data name="category/data/apply_design_to_products" xsi:type="string">Yes</data> - <data name="category/data/schedule_update_from" xsi:type="string">Jan 10, 2014</data> - <data name="category/data/schedule_update_to" xsi:type="string">Dec 31, 2024</data> + <data name="category/data/schedule_update_from" xsi:type="string">01/10/2014</data> + <data name="category/data/schedule_update_to" xsi:type="string">12/31/2024</data> <constraint name="Magento\Catalog\Test\Constraint\AssertCategorySaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryForm" /> </variation> @@ -82,8 +82,8 @@ <data name="category/data/layout_update_xml" xsi:type="string"><referenceContainer name="content.aside" remove="true"/></data> <data name="category/data/new_theme" xsi:type="string">Magento Luma</data> <data name="category/data/apply_design_to_products" xsi:type="string">Yes</data> - <data name="category/data/schedule_update_from" xsi:type="string">Jan 1, 2014</data> - <data name="category/data/schedule_update_to" xsi:type="string">Dec 31, 2024</data> + <data name="category/data/schedule_update_from" xsi:type="string">01/10/2014</data> + <data name="category/data/schedule_update_to" xsi:type="string">12/31/2024</data> <constraint name="Magento\Catalog\Test\Constraint\AssertCategorySaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryPage" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml index e46ad32551e7496f8ffb443543612567304ce5ee..9e410c525deef720573e7c93e0de4d463b9b2ac7 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityTest.xml @@ -168,7 +168,7 @@ <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> - <data name="product/data/price/value" xsi:type="string">10008</data> + <data name="product/data/price/value" xsi:type="string">10008.88</data> <data name="product/data/short_description" xsi:type="string">Simple Product short_description %isolation%</data> <data name="product/data/description" xsi:type="string">Simple Product description %isolation%</data> <data name="product/data/weight" xsi:type="string">58</data> @@ -461,5 +461,44 @@ <data name="product/data/country_of_manufacture" xsi:type="string">Antarctica</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> </variation> + <variation name="CreateSimpleProductEntityTestVariation28" summary="Create product with tier price for not logged in customer"> + <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> + <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> + <data name="product/data/price/value" xsi:type="string">100</data> + <data name="product/data/weight" xsi:type="string">50</data> + <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">667</data> + <data name="product/data/tier_price/dataset" xsi:type="string">not_logged_in</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductTierPriceOnProductPage" /> + </variation> + <variation name="CreateSimpleProductEntityTestVariation29" summary="Create Simple Product and assign it to custom website"> + <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> + <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> + <data name="product/data/price/value" xsi:type="string">200.20</data> + <data name="product/data/weight" xsi:type="string">50</data> + <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">668</data> + <data name="product/data/website_ids/0/dataset" xsi:type="string">custom_store</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductOnCustomWebsite" /> + </variation> + <variation name="CreateSimpleProductEntityWithEmptySkuMaskTest1" summary="Create Simple Product Entity With Empty Sku Mask" ticketId="MAGETWO-58951"> + <data name="configData" xsi:type="string">empty_product_mask_sku</data> + <data name="description" xsi:type="string">Create product with custom options(fixed price)</data> + <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> + <data name="product/data/sku" xsi:type="string">simple_sku_%isolation%</data> + <data name="product/data/price/value" xsi:type="string">10000</data> + <data name="product/data/weight" xsi:type="string">50</data> + <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">657</data> + <data name="product/data/checkout_data/dataset" xsi:type="string">simple_drop_down_with_one_option_fixed_price</data> + <data name="product/data/price/dataset" xsi:type="string">drop_down_with_one_option_fixed_price</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCategory" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCart" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.php index f7fd82e3342bfdce8760c253215ad8189ef33643..9c38ff1b479fe6a02f2217572bea170b1906f00d 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.php @@ -6,10 +6,13 @@ namespace Magento\Catalog\Test\TestCase\Product; +use Magento\Mtf\Fixture\FixtureFactory; +use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\TestCase\Injectable; use Magento\Catalog\Test\Fixture\CatalogProductSimple; use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; use Magento\Catalog\Test\Page\Adminhtml\CatalogProductActionAttributeEdit; +use Magento\Mtf\TestStep\TestStepFactory; /** * Precondition: @@ -58,35 +61,58 @@ class MassProductUpdateTest extends Injectable */ protected $configData; + /** + * Factory for Test Steps. + * + * @var TestStepFactory + */ + private $testStepFactory; + + /** + * Factory for Fixtures. + * + * @var FixtureFactory + */ + private $fixtureFactory; + /** * Injection data. * * @param CatalogProductIndex $productGrid * @param CatalogProductActionAttributeEdit $attributeMassActionPage + * @param TestStepFactory $testStepFactory + * @param FixtureFactory $fixtureFactory * @return void */ public function __inject( CatalogProductIndex $productGrid, - CatalogProductActionAttributeEdit $attributeMassActionPage + CatalogProductActionAttributeEdit $attributeMassActionPage, + TestStepFactory $testStepFactory, + FixtureFactory $fixtureFactory ) { $this->productGrid = $productGrid; $this->attributeMassActionPage = $attributeMassActionPage; + $this->testStepFactory = $testStepFactory; + $this->fixtureFactory = $fixtureFactory; } /** * Run mass update product simple entity test. * - * @param CatalogProductSimple $initialProduct * @param CatalogProductSimple $product * @param string $configData + * @param array $initialProducts * @return array */ - public function test(CatalogProductSimple $initialProduct, CatalogProductSimple $product, $configData) + public function test(CatalogProductSimple $product, $configData, array $initialProducts) { $this->configData = $configData; // Preconditions - $initialProduct->persist(); + $products = $this->testStepFactory->create( + \Magento\Catalog\Test\TestStep\CreateProductsStep::class, + ['products' => $initialProducts] + )->run()['products']; $this->objectManager->create( \Magento\Config\Test\TestStep\SetupConfigurationStep::class, @@ -95,19 +121,33 @@ class MassProductUpdateTest extends Injectable // Steps $this->productGrid->open(); - $this->productGrid->getProductGrid()->updateAttributes([['sku' => $initialProduct->getSku()]]); + $this->productGrid->getProductGrid()->updateAttributes($products); $this->attributeMassActionPage->getAttributesBlockForm()->fill($product); $this->attributeMassActionPage->getFormPageActions()->save(); - $data = array_merge($initialProduct->getData(), $product->getData()); - $product = $this->objectManager->create( - \Magento\Catalog\Test\Fixture\CatalogProductSimple::class, - ['data' => $data] - ); - - return [ - 'category' => $initialProduct->getDataFieldConfig('category_ids')['source']->getCategories()[0], - 'product' => $product, - ]; + $updatedProducts = $this->prepareUpdatedProducts($products, $product); + + return ['products' => $updatedProducts]; + } + + /** + * Prepare updated products. + * + * @param array $products + * @param CatalogProductSimple $product + * @return array + */ + private function prepareUpdatedProducts(array $products, CatalogProductSimple $product) + { + $productsReturn = []; + /** @var FixtureInterface $item */ + foreach ($products as $item) { + $productsReturn[] = $this->fixtureFactory->create( + get_class($item), + ['data' => array_merge($item->getData(), $product->getData())] + ); + } + + return $productsReturn; } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml index 6f937999f3c34cc172b3552b6280b7563d07a1d1..5435ea89669f6f1a3d0d3d98cb8e1a5bb5aabed0 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml @@ -9,11 +9,12 @@ <testCase name="Magento\Catalog\Test\TestCase\Product\MassProductUpdateTest" summary="Edit Products Using Mass Actions" ticketId="MAGETWO-21128"> <variation name="MassProductPriceUpdateTestVariation1"> <data name="configData" xsi:type="string">product_flat</data> - <data name="initialProduct/dataset" xsi:type="string">simple_10_dollar</data> + <data name="initialProducts/0" xsi:type="string">catalogProductSimple::simple_10_dollar</data> + <data name="initialProducts/1" xsi:type="string">catalogProductSimple::simple_10_dollar</data> + <data name="productsCount" xsi:type="number">2</data> <data name="product/data/price/value" xsi:type="string">1.99</data> <constraint name="Magento\Catalog\Test\Constraint\AssertMassProductUpdateSuccessMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCategory" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml index 323246949d1a55825bfe49317af6662be585d15c..0395f9f5ed8cf83c0aa570252cc5ed7df32da3c8 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml @@ -145,5 +145,19 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCategory" /> </variation> + <variation name="EditSimpleProductTestVariation11" summary="Update simple product with custom option"> + <data name="initialProduct/dataset" xsi:type="string">product_with_category</data> + <data name="product/data/name" xsi:type="string">Test simple product %isolation%</data> + <data name="product/data/sku" xsi:type="string">test_simple_product_%isolation%</data> + <data name="product/data/price/value" xsi:type="string">245.00</data> + <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">200</data> + <data name="product/data/custom_options/dataset" xsi:type="string">drop_down_with_one_option_percent_price</data> + <data name="product/data/checkout_data/dataset" xsi:type="string">simple_drop_down_with_one_option_percent_price</data> + <data name="product/data/url_key" xsi:type="string">test-simple-product-%isolation%</data> + <data name="product/data/weight" xsi:type="string">120.0000</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCart" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php index e8178d07a1e9e26b6494d0b06551149636e4e4c1..697733fd3c33d8856f5d775452aca8ad977e77e9 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php @@ -129,7 +129,7 @@ class Cart extends Block Locator::SELECTOR_XPATH ); $cartItem = $this->blockFactory->create( - \Magento\Checkout\Test\Block\Cart\CartItem::class, + '\\' . get_class($this) . '\CartItem', ['element' => $cartItemBlock] ); } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php index 2cf6d5ad87dfff1d9f5c96b9ed40414abb822d44..240ae1c183201f2c479564b5dfad59a274e84b9d 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Totals.php @@ -240,7 +240,7 @@ class Totals extends Block */ public function isVisibleShippingPriceBlock() { - return $this->_rootElement->find($this->shippingPriceBlockSelector, Locator::SELECTOR_CSS)->isVisible(); + return $this->_rootElement->find($this->shippingPriceBlockSelector, Locator::SELECTOR_CSS)->isVisible(); } /** diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPageOnFrontend.php b/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPageOnFrontend.php new file mode 100644 index 0000000000000000000000000000000000000000..401042c031259f4c9e473e8ed7c0fbabb063b5fc --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPageOnFrontend.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Cms\Test\Constraint; + +use Magento\Cms\Test\Fixture\CmsPage; +use Magento\Cms\Test\Page\CmsPage as FrontCmsPage; +use Magento\Mtf\Client\BrowserInterface; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert that created CMS page with expected contents displayed on Frontend. + */ +class AssertCmsPageOnFrontend extends AbstractConstraint +{ + /** + * Assert that created CMS page with expected contents displayed on Frontend. + * + * @param CmsPage $cms + * @param FrontCmsPage $frontCmsPage, + * @param BrowserInterface $browser + * @param string $displayContent + * @return void + */ + public function processAssert( + CmsPage $cms, + FrontCmsPage $frontCmsPage, + BrowserInterface $browser, + $displayContent = null + ) { + $browser->open($_ENV['app_frontend_url'] . $cms->getIdentifier()); + $fixtureContent = $cms->getContent(); + \PHPUnit_Framework_Assert::assertContains( + $displayContent != null ? $displayContent : $fixtureContent['content'], + $frontCmsPage->getCmsPageBlock()->getPageContent(), + 'Wrong content is displayed.' + ); + } + + /** + * CMS Page content equals to data from fixture. + * + * @return string + */ + public function toString() + { + return 'CMS Page content equals to data from fixture.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPagePreview.php b/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPagePreview.php index 51e9b428892cc53a2db873550f0e0c76d6d997d0..6db35e88efe49e92043b4d85127c74510fb18926 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPagePreview.php +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Constraint/AssertCmsPagePreview.php @@ -30,6 +30,7 @@ class AssertCmsPagePreview extends AbstractConstraint * @param FrontCmsPage $frontCmsPage * @param CmsPage $cms * @param BrowserInterface $browser + * @param string $displayContent * @return void */ public function processAssert( @@ -37,7 +38,8 @@ class AssertCmsPagePreview extends AbstractConstraint FrontCmsIndex $frontCmsIndex, FrontCmsPage $frontCmsPage, CmsPage $cms, - BrowserInterface $browser + BrowserInterface $browser, + $displayContent = null ) { $cmsIndex->open(); $filter = ['title' => $cms->getTitle()]; @@ -46,7 +48,7 @@ class AssertCmsPagePreview extends AbstractConstraint $fixtureContent = $cms->getContent(); \PHPUnit_Framework_Assert::assertContains( - $fixtureContent['content'], + $displayContent != null ? $displayContent : $fixtureContent['content'], $frontCmsPage->getCmsPageBlock()->getPageContent(), 'Wrong content is displayed.' ); diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml index 67b4421182848ebb9b2633c4d9212e6679cccf51..f8b6c8c3d9e06da90b122ba39d0c3f01eebc1978 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityTest.xml @@ -54,5 +54,18 @@ <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageSuccessSaveMessage" /> <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageDisabledOnFrontend" /> </variation> + <variation name="CreateCmsPageEntityTestVariation5" summary="Block Cache Exploit" ticketId="MAGETWO-48017"> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="fixtureType" xsi:type="string">cmsPage</data> + <data name="data/title" xsi:type="string">NewCmsPage%isolation%</data> + <data name="data/identifier" xsi:type="string">identifier-%isolation%</data> + <data name="data/store_id" xsi:type="string">Main Website/Main Website Store/Default Store View</data> + <data name="data/is_active" xsi:type="string">Yes</data> + <data name="data/content/content" xsi:type="string">\\{{block class='Magento\Framework\View\Element\Text' text='bla bla bla' cache_key='BACKEND_ACL_RESOURCES' cache_lifetime=999\}}</data> + <data name="displayContent" xsi:type="string">bla bla bla</data> + <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageSuccessSaveMessage" /> + <constraint name="Magento\Cms\Test\Constraint\AssertCmsPagePreview" /> + <constraint name="Magento\Cms\Test\Constraint\AssertCmsPageOnFrontend" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/etc/di.xml index f4b96844b27383679f0f2a65f63e5445b4e9b6e7..0d3e1b4ea97103d1fb941843f3c419b62f20d49b 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/etc/di.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/etc/di.xml @@ -6,24 +6,84 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\Cms\Test\Constraint\AssertCmsBlockSuccessSaveMessage"> + <type name="Magento\Cms\Test\Constraint\AssertCmsBlockDeleteMessage"> <arguments> - <argument name="severity" xsi:type="string">high</argument> + <argument name="severity" xsi:type="string">S1</argument> </arguments> </type> - <type name="Magento\Cms\Test\Constraint\AssertCmsBlockOnCategoryPage"> + <type name="Magento\Cms\Test\Constraint\AssertCmsBlockInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsBlockNotInGrid"> <arguments> - <argument name="severity" xsi:type="string">high</argument> + <argument name="severity" xsi:type="string">S3</argument> </arguments> </type> <type name="Magento\Cms\Test\Constraint\AssertCmsBlockNotOnCategoryPage"> <arguments> - <argument name="severity" xsi:type="string">high</argument> + <argument name="severity" xsi:type="string">S3</argument> </arguments> </type> - <type name="Magento\Cms\Test\Constraint\AssertCmsBlockDeleteMessage"> + <type name="Magento\Cms\Test\Constraint\AssertCmsBlockOnCategoryPage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsBlockSuccessSaveMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageDeleteMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageDisabledOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageDuplicateErrorMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageForm"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageNotInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPagePreview"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertCmsPageSuccessSaveMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Cms\Test\Constraint\AssertUrlRewriteCmsPageRedirect"> <arguments> - <argument name="severity" xsi:type="string">high</argument> + <argument name="severity" xsi:type="string">S1</argument> </arguments> </type> </config> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Grid.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Grid.php new file mode 100644 index 0000000000000000000000000000000000000000..e6fa3271ea6cd60d1f8ae7e509a3c873344e7e7d --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Grid.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Test\Block\Adminhtml\Product; + +use Magento\ConfigurableProduct\Test\Fixture\ConfigurableProduct; + +/** + * Backend catalog product grid. + */ +class Grid extends \Magento\Catalog\Test\Block\Adminhtml\Product\Grid +{ + /** + * Prepare data. + * + * @param ConfigurableProduct $product + * @return array + */ + public function prepareData($product) + { + $configurableAttributesData = $product->getConfigurableAttributesData(); + $productItems[] = ['sku' => $product->getSku()]; + foreach ($configurableAttributesData['matrix'] as $variation) { + $productItems[] = ['sku' => $variation['sku']]; + } + + return $productItems; + } +} diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml index 2a3703a10607554c2682f19b036201cc700dc477..4a69a7604bca3d74119b07bdcea3b2115a1f6648 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct.xml @@ -41,6 +41,35 @@ </field> </dataset> + <dataset name="out_of_stock"> + <field name="name" xsi:type="string">Test configurable product %isolation%</field> + <field name="sku" xsi:type="string">sku_test_configurable_product_%isolation%</field> + <field name="price" xsi:type="array"> + <item name="value" xsi:type="string">40</item> + <item name="dataset" xsi:type="string">price_40</item> + </field> + <field name="status" xsi:type="string">Yes</field> + <field name="visibility" xsi:type="string">Catalog, Search</field> + <field name="tax_class_id" xsi:type="array"> + <item name="dataset" xsi:type="string">taxable_goods</item> + </field> + <field name="url_key" xsi:type="string">configurable-product-%isolation%</field> + <field name="configurable_attributes_data" xsi:type="array"> + <item name="dataset" xsi:type="string">two_options_with_assigned_product</item> + </field> + <field name="quantity_and_stock_status" xsi:type="array"> + <item name="is_in_stock" xsi:type="string">Out of Stock</item> + </field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </item> + </field> + <field name="attribute_set_id" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </field> + </dataset> + <dataset name="configurable_with_qty_1"> <field name="name" xsi:type="string">Test configurable product %isolation%</field> <field name="sku" xsi:type="string">sku_test_configurable_product_%isolation%</field> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/MassProductUpdateTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/MassProductUpdateTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..3d58645d618ffe4f34beb84cd3a1d58212649148 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/MassProductUpdateTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Catalog\Test\TestCase\Product\MassProductUpdateTest" summary="Edit Products Using Mass Actions" ticketId="MAGETWO-21128"> + <variation name="MassProductUpdateTestVariation2" summary="Update stock data for simple and configurable"> + <data name="configData" xsi:type="string">product_flat</data> + <data name="initialProducts/1" xsi:type="string">configurableProduct::out_of_stock</data> + <data name="initialProducts/0" xsi:type="string">catalogProductSimple::out_of_stock</data> + <data name="productsCount" xsi:type="number">2</data> + <data name="product/data/stock_data" xsi:type="string">In Stock</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertMassProductUpdateSuccessMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductsInStock" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.php index 9f05188e2ebb483ea16edc9ccb8eab15f309c781..e1e8f2ddebb709e17d8a977668f420d803247558 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.php @@ -110,4 +110,92 @@ class CustomerForm extends Form $mapping['lastname']['strategy'] )->getValue(); } + + /** + * Set 'Change Email' checkbox value. + * + * @param boolean $value + * @return void + */ + public function setChangeEmail($value) + { + $mapping = $this->dataMapping(); + $this->_rootElement->find( + $mapping['change_email']['selector'], + $mapping['change_email']['strategy'], + 'checkbox' + )->setValue($value ? "Yes" : "No"); + } + + /** + * Set 'Change Password' checkbox value. + * + * @param boolean $value + * @return void + */ + public function setChangePassword($value) + { + $mapping = $this->dataMapping(); + $this->_rootElement->find( + $mapping['change_password']['selector'], + $mapping['change_password']['strategy'], + 'checkbox' + )->setValue($value ? "Yes" : "No"); + } + + /** + * Check if Current Password field is visible. + * + * @return boolean + */ + public function isCurrentPasswordVisible() + { + $mapping = $this->dataMapping(); + return $this->_rootElement->find( + $mapping['current_password']['selector'], + $mapping['current_password']['strategy'] + )->isVisible(); + } + + /** + * Check if Password field is visible. + * + * @return boolean + */ + public function isPasswordVisible() + { + $mapping = $this->dataMapping(); + return $this->_rootElement->find( + $mapping['password']['selector'], + $mapping['password']['strategy'] + )->isVisible(); + } + + /** + * Check if Confirmation field is visible. + * + * @return boolean + */ + public function isConfirmPasswordVisible() + { + $mapping = $this->dataMapping(); + return $this->_rootElement->find( + $mapping['confirmation']['selector'], + $mapping['confirmation']['strategy'] + )->isVisible(); + } + + /** + * Check if Email field is visible. + * + * @return boolean + */ + public function isEmailVisible() + { + $mapping = $this->dataMapping(); + return $this->_rootElement->find( + $mapping['email']['selector'], + $mapping['email']['strategy'] + )->isVisible(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.xml index 9bbc247787e62322e5178bf486dce1ee80097356..20610b0672722a5b53cf8028593b2f457412b673 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Form/CustomerForm.xml @@ -9,6 +9,12 @@ <fields> <firstname /> <lastname /> + <change_email> + <input>checkbox</input> + </change_email> + <change_password> + <input>checkbox</input> + </change_password> <email /> <current_password /> <password /> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/CustomerAccountEdit.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/CustomerAccountEdit.xml index 66a7e57279113b3fe8318498279398daccf9f296..362e66aa19d43ddb361bb4eb790d766d3d10d9a3 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/CustomerAccountEdit.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/CustomerAccountEdit.xml @@ -9,5 +9,6 @@ <page name="CustomerAccountEdit" mca="customer/account/edit" module="Magento_Customer"> <block name="accountInfoForm" class="Magento\Customer\Test\Block\Form\CustomerForm" locator="#form-validate" strategy="css selector"/> <block name="messages" class="Magento\Backend\Test\Block\Messages" locator=".page.messages" strategy="css selector"/> + <block name="accountMenuBlock" class="Magento\Customer\Test\Block\Account\Links" locator=".nav.items" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/etc/testcase.xml index 9880acee6bbccfb7c3569d136a3f1cf4ce479055..d34a96100f74e9b087ed3415e21a571dadb5ca44 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/etc/testcase.xml @@ -13,7 +13,8 @@ <step name="createProducts" module="Magento_Catalog" next="addProducts"/> <step name="addProducts" module="Magento_Sales" next="updateProductsData"/> <step name="updateProductsData" module="Magento_Sales" next="fillBillingAddress"/> - <step name="fillBillingAddress" module="Magento_Sales" next="selectPaymentMethodForOrder"/> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress"/> + <step name="fillShippingAddress" module="Magento_Sales" next="selectPaymentMethodForOrder"/> <step name="selectPaymentMethodForOrder" module="Magento_Sales" next="selectShippingMethodForOrder"/> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="submitOrder"/> <step name="submitOrder" module="Magento_Sales"/> diff --git a/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml index 7d4a961b7686733323d2683ef335c72223f6b460..9bec14fd9575d945218bffd258cde0d06b766d92 100644 --- a/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Dhl/Test/TestCase/OnePageCheckoutTest.xml @@ -20,7 +20,7 @@ <data name="cart/data/shipping_method" xsi:type="string">Express easy</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, dhl_eu, shipping_origin_CH, config_base_currency_ch</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml index 9d6b7d119e09ce6f65529c028b24e190d0f8d42f..fcf8331d18ba7fd02427602013b1674aadfb11fd 100644 --- a/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Fedex/Test/TestCase/OnePageCheckoutTest.xml @@ -20,7 +20,7 @@ <data name="cart/data/shipping_method" xsi:type="string">International Economy</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, fedex, shipping_origin_US_CA</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> @@ -39,7 +39,7 @@ <data name="cart/data/shipping_method" xsi:type="string">Ground</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, fedex, shipping_origin_US_CA</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Block/Devdocs.php b/dev/tests/functional/tests/app/Magento/Install/Test/Block/Devdocs.php new file mode 100644 index 0000000000000000000000000000000000000000..1be88c0bbf7b89f7dbf529456a9ee1235df1e163 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Block/Devdocs.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Install\Test\Block; + +use Magento\Mtf\Block\Block; + +/** + * Developer Documentation block. + */ +class Devdocs extends Block +{ + /** + * Developer Documentation title. + * + * @var string + */ + protected $devdocsTitle = '.page-heading'; + + /** + * Get Developer Documentation title text. + * + * @return string + */ + public function getDevdocsTitle() + { + return $this->_rootElement->find($this->devdocsTitle)->getText(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Block/Landing.php b/dev/tests/functional/tests/app/Magento/Install/Test/Block/Landing.php index 5b92b332b26960fdfc0e285459e39b78f5c72f67..d809fb861b66c92138a3231aeae8a7bd35ea15d9 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Block/Landing.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Block/Landing.php @@ -14,6 +14,13 @@ use Magento\Mtf\Client\Locator; */ class Landing extends Block { + /** + * Link by text. + * + * @var string + */ + protected $linkSelector = '//a[text()="%s"]'; + /** * 'Agree and Set up Magento' button. * @@ -47,4 +54,15 @@ class Landing extends Block { $this->_rootElement->find($this->termsAndAgreement, Locator::SELECTOR_CSS)->click(); } + + /** + * Click on link. + * + * @param string $text + * @return void + */ + public function clickLink($text) + { + $this->_rootElement->find(sprintf($this->linkSelector, $text), Locator::SELECTOR_XPATH)->click(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertDevdocsLink.php b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertDevdocsLink.php new file mode 100644 index 0000000000000000000000000000000000000000..d397cc1882b6f39c2fa5423a9ccecbceec002aaa --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertDevdocsLink.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Install\Test\Constraint; + +use Magento\Install\Test\Page\DevdocsInstall; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Check Developer Documentation link. + */ +class AssertDevdocsLink extends AbstractConstraint +{ + /** + * Developer Documentation title. + */ + const DEVDOCS_TITLE_TEXT = 'Setup Wizard installation'; + + /** + * Check Developer Documentation link. + * + * @param DevdocsInstall $devdocsInstallPage + * @return void + */ + public function processAssert(DevdocsInstall $devdocsInstallPage) + { + \PHPUnit_Framework_Assert::assertEquals( + self::DEVDOCS_TITLE_TEXT, + $devdocsInstallPage->getDevdocsBlock()->getDevdocsTitle(), + 'Developer Documentation link is wrong.' + ); + } + + /** + * Returns a string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return "Developer Documentation link is correct."; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml b/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml new file mode 100644 index 0000000000000000000000000000000000000000..d2dc975cc2b2bd82a72b6b9e4e1d71b97fd635b4 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/pages.xsd"> + <page name="DevdocsInstall" mca="http://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" module="Magento_Install"> + <block name="devdocsBlock" class="Magento\Install\Test\Block\Devdocs" locator="body" strategy="css selector"/> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php index f7931b03e3def69ddb02f6c73c7c0381fd8cf0c1..ee51c4eee82271b2744d63167d56b82e526049a9 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/TestCase/InstallTest.php @@ -7,6 +7,7 @@ namespace Magento\Install\Test\TestCase; use Magento\Install\Test\Page\Install; +use Magento\Install\Test\Page\DevdocsInstall; use Magento\Install\Test\Fixture\Install as InstallConfig; use Magento\User\Test\Fixture\User; use Magento\Mtf\Fixture\FixtureFactory; @@ -14,6 +15,9 @@ use Magento\Mtf\TestCase\Injectable; use Magento\Install\Test\Constraint\AssertAgreementTextPresent; use Magento\Install\Test\Constraint\AssertSuccessfulReadinessCheck; use Magento\Install\Test\Constraint\AssertAdminUriAutogenerated; +use Magento\Install\Test\Constraint\AssertDevdocsLink; +use Magento\Mtf\Util\Command\Cli\Setup; +use Magento\Mtf\Client\BrowserInterface; /** * PLEASE ADD NECESSARY INFO BEFORE RUNNING TEST TO @@ -24,24 +28,47 @@ use Magento\Install\Test\Constraint\AssertAdminUriAutogenerated; * * Steps: * 1. Go setup landing page. - * 2. Click on "Terms and agreements" button. - * 3. Check license agreement text. - * 4. Return back to landing page and click "Agree and Setup" button. - * 5. Click "Start Readiness Check" button. - * 6. Make sure PHP Version, PHP Extensions and File Permission are ok. - * 7. Click "Next" and fill DB credentials. - * 8. Click "Test Connection and Authentication" and make sure connection successful. - * 9. Click "Next" and fill store address and admin path. - * 10. Click "Next" and leave all default values. - * 11. Click "Next" and fill admin user info. - * 12. Click "Next" and on the "Step 6: Install" page click "Install Now" button. - * 13. Perform assertions. + * 2. Click on Developer Documentation link. + * 3. Check Developer Documentation title. + * 4. Click on "Terms and agreements" button. + * 5. Check license agreement text. + * 6. Return back to landing page and click "Agree and Setup" button. + * 7. Click "Start Readiness Check" button. + * 8. Make sure PHP Version, PHP Extensions and File Permission are ok. + * 9. Click "Next" and fill DB credentials. + * 10. Click "Test Connection and Authentication" and make sure connection successful. + * 11. Click "Next" and fill store address and admin path. + * 12. Click "Next" and leave all default values. + * 13. Click "Next" and fill admin user info. + * 14. Click "Next" and on the "Step 6: Install" page click "Install Now" button. + * 15. Perform assertions. * * @group Installer_and_Upgrade/Downgrade * @ZephyrId MAGETWO-31431 + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class InstallTest extends Injectable { + /** + * Developer Documentation link text. + */ + const DEVDOCS_LINK_TEXT = 'Getting Started'; + + /** + * Developer Documentation install page. + * + * @var DevdocsInstall + */ + protected $devdocsInstallPage; + + /** + * Terms and agreement selector. + * + * @var string + */ + protected $termsLink = '.text-terms>a'; + /** * Install page. * @@ -72,14 +99,16 @@ class InstallTest extends Injectable * Uninstall Magento. * * @param Install $installPage + * @param Setup $magentoSetup + * @param DevdocsInstall $devdocsInstallPage * @return void */ - public function __inject(Install $installPage) + public function __inject(Install $installPage, Setup $magentoSetup, DevdocsInstall $devdocsInstallPage) { - $magentoBaseDir = dirname(dirname(dirname(MTF_BP))); // Uninstall Magento. - shell_exec("php -f $magentoBaseDir/bin/magento setup:uninstall -n"); + $magentoSetup->uninstall(); $this->installPage = $installPage; + $this->devdocsInstallPage = $devdocsInstallPage; } /** @@ -91,6 +120,8 @@ class InstallTest extends Injectable * @param AssertAgreementTextPresent $assertLicense * @param AssertSuccessfulReadinessCheck $assertReadiness * @param AssertAdminUriAutogenerated $assertAdminUri + * @param AssertDevdocsLink $assertDevdocsLink + * @param BrowserInterface $browser * @param array $install [optional] * @return array */ @@ -101,6 +132,8 @@ class InstallTest extends Injectable AssertAgreementTextPresent $assertLicense, AssertSuccessfulReadinessCheck $assertReadiness, AssertAdminUriAutogenerated $assertAdminUri, + AssertDevdocsLink $assertDevdocsLink, + BrowserInterface $browser, array $install = [] ) { $dataConfig = array_merge($install, $configData); @@ -111,6 +144,14 @@ class InstallTest extends Injectable $installConfig = $fixtureFactory->create(\Magento\Install\Test\Fixture\Install::class, ['data' => $dataConfig]); // Steps $this->installPage->open(); + // Verify Developer Documentation link. + $handle = $browser->getCurrentWindow(); + $this->installPage->getLandingBlock()->clickLink(self::DEVDOCS_LINK_TEXT); + $this->waitTillTermsLinkNotVisible($browser); + $docHandle = $browser->getCurrentWindow(); + $assertDevdocsLink->processAssert($this->devdocsInstallPage); + $browser->closeWindow($docHandle); + $browser->selectWindow($handle); // Verify license agreement. $this->installPage->getLandingBlock()->clickTermsAndAgreement(); $assertLicense->processAssert($this->installPage); @@ -139,4 +180,20 @@ class InstallTest extends Injectable return ['installConfig' => $installConfig]; } + + /** + * Wait till terms link is not visible. + * + * @param BrowserInterface $browser + * @return void + */ + private function waitTillTermsLinkNotVisible(BrowserInterface $browser) + { + $browser->waitUntil( + function () use ($browser) { + $browser->selectWindow(); + return $browser->find($this->termsLink)->isVisible() ? null : true; + } + ); + } } diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/Constraint/AssertCardRequiredFields.php b/dev/tests/functional/tests/app/Magento/Payment/Test/Constraint/AssertCardRequiredFields.php new file mode 100644 index 0000000000000000000000000000000000000000..2b1d9bdcf2df8fdb32869731b7b3af05d47bc417 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/Constraint/AssertCardRequiredFields.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Payment\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Payment\Test\Repository\CreditCardAdmin; +use Magento\Sales\Test\Page\Adminhtml\OrderCreateIndex; + +/** + * Class AssertCardRequiredFields + * + * Assert that fields are active. + */ +class AssertCardRequiredFields extends AbstractConstraint +{ + /** + * Expected required field message. + */ + const REQUIRE_MESSAGE = 'This is a required field.'; + + /** + * Expected required valid number message. + */ + const VALID_NUMBER_MESSAGE = 'Please enter a valid number in this field.'; + + /** + * Assert required fields on credit card payment method in backend. + * @param OrderCreateIndex $orderCreateIndex + * @param CreditCardAdmin $creditCard + * @return void + */ + public function processAssert(OrderCreateIndex $orderCreateIndex, CreditCardAdmin $creditCard) + { + $actualRequiredFields = $orderCreateIndex->getCreateBlock()->getBillingMethodBlock() + ->getJsErrors(); + $creditCardEmpty = $creditCard->get('visa_empty'); + foreach (array_keys($creditCardEmpty) as $field) { + \PHPUnit_Framework_Assert::assertTrue( + isset($actualRequiredFields[$field]), + "Field '$field' is not highlighted with an JS error." + ); + $expected = self::REQUIRE_MESSAGE; + if (in_array($field, ['cc_number', 'cc_cid'])) { + $expected = self::VALID_NUMBER_MESSAGE; + } + \PHPUnit_Framework_Assert::assertEquals( + $expected, + $actualRequiredFields[$field], + "Field '$field' is not highlighted as required." + ); + } + } + + /** + * Returns string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return 'All required fields on customer form are highlighted.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCardAdmin.xml b/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCardAdmin.xml index 1109b70eec7cc14dd343e06fb21c8d8f0eb9c52b..371c8f350c6af028ddc544c23142f2886d699dbd 100644 --- a/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCardAdmin.xml +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/Repository/CreditCardAdmin.xml @@ -22,5 +22,11 @@ <field name="cc_exp_year" xsi:type="string">2020</field> <field name="cc_cid" xsi:type="string">123</field> </dataset> + <dataset name="visa_empty"> + <field name="cc_type" xsi:type="string">Please Select</field> + <field name="cc_number" xsi:type="string" /> + <field name="cc_exp_month" xsi:type="string">Month</field> + <field name="cc_cid" xsi:type="string" /> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.php b/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.php index b1c2d71a43bdd7d77cabb00d010261245b599d00..37d85bd91b1d68ddea5ae0c1fd250547810d6d19 100644 --- a/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.php +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.php @@ -16,6 +16,7 @@ class ConflictResolutionTest extends Scenario /* tags */ const MVP = 'no'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.xml b/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.xml index dc2ba760025fb71427e2ea50a7e3cce4e1c305e5..163c33f7306bac0f8d859ea9cddbf98e6e2eb2a3 100644 --- a/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.xml +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/TestCase/ConflictResolutionTest.xml @@ -40,7 +40,7 @@ <item name="Authorize.net Direct Post" xsi:type="string"/> </item> </data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S2</data> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Payment/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Payment/Test/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..fc6a35a7c52c1b91792252cf08d13ff20d50a88f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Payment/Test/etc/di.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. 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\Payment\Test\Constraint\AssertCardRequiredFields"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Payment\Test\Constraint\AssertFieldsAreActive"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Payment\Test\Constraint\AssertFieldsAreDisabled"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Payment\Test\Constraint\AssertFieldsAreEnabled"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Payment\Test\Constraint\AssertFieldsArePresent"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreatePayFlowOrderBackendNegativeTest.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreatePayFlowOrderBackendNegativeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3e510ab12ec3219a10ccabf6d20c0e2e8e70e0ba --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreatePayFlowOrderBackendNegativeTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Paypal\Test\TestCase; + +use Magento\Mtf\TestCase\Scenario; + +/** + * Preconditions: + * 1. Create customer. + * 2. Create product. + * + * Steps: + * 1. Open Backend. + * 2. Open Sales -> Orders. + * 3. Click Create New Order. + * 4. Select Customer created in preconditions. + * 5. Add Product. + * 6. Fill data according dataset. + * 7. Click Update Product qty. + * 8. Fill data according dataset. + * 9. Click Get Shipping Method and rates. + * 10. Fill data according dataset. + * 11. Select payment method Credit Card (PayFlow Pro) + * 12. Leave empty required fields for credit card + * 13. Click Submit order button + * + * @group PayPal + * @ZephyrId MAGETWO-58934 + */ +class CreatePayFlowOrderBackendNegativeTest extends Scenario +{ + /* tags */ + const MVP = 'yes'; + const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S2'; + /* end tags */ + + /** + * Runs sales order on backend. + * + * @return void + */ + public function test() + { + $this->executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreatePayFlowOrderBackendNegativeTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreatePayFlowOrderBackendNegativeTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..4ad751de68a71f0330aa547bf74481c3e306f379 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreatePayFlowOrderBackendNegativeTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Paypal\Test\TestCase\CreatePayFlowOrderBackendNegativeTest" summary="Verify required fields validation for PayPal Payflow Pro credit card on admin order creation" ticketId="MAGETWO-58934"> + <variation name="CreatePayFlowOrderBackendNegativeTestPayflowProVariation1" summary="Verify required fields validation for credit card on admin order creation" ticketId="MAGETWO-58934"> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S2</data> + <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="billingAddress/dataset" xsi:type="string">US_address_1_without_email</data> + <data name="saveAddress" xsi:type="string">No</data> + <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> + <data name="shipping/shipping_method" xsi:type="string">Fixed</data> + <data name="prices" xsi:type="array"> + <item name="grandTotal" xsi:type="string">15.00</item> + </data> + <data name="payment/method" xsi:type="string">payflowpro</data> + <data name="creditCardClass" xsi:type="string">credit_card_admin</data> + <data name="creditCard/dataset" xsi:type="string">visa_empty</data> + <data name="creditCardSave" xsi:type="string">Yes</data> + <data name="configData" xsi:type="string">payflowpro, payflowpro_use_vault</data> + <constraint name="Magento\Payment\Test\Constraint\AssertCardRequiredFields" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml index 64a191040b8a451aede65fe73dc9785747509c4f..38666c36a2ba4e53641f45616060353ca5e5e22c 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Vault\Test\TestCase\CreateVaultOrderBackendTest" summary="Use saved for PayPal Payflow Pro credit card on admin order creation" ticketId="MAGETWO-47350"> <variation name="CreateVaultOrderBackendTestPayflowProVariation1" summary="Use saved for PayPal Payflow Pro credit card on admin order creation" ticketId="MAGETWO-47350"> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="customer/dataset" xsi:type="string">default</data> <data name="billingAddress/dataset" xsi:type="string">US_address_1_without_email</data> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.php index c8fb3e4f30f19834d1587cf1a1290aeeace7b958..8a10d08e0f6c3e6cdfddc7ec1a52d8796d54fca3 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.php @@ -31,6 +31,7 @@ class ExpressCheckoutFromProductPageTest extends Scenario const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; const TO_MAINTAIN = 'yes'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml index 4373523471cffc5e8e13b6bc01dffc9902516cd0..1d92fa149efbc336eb0910a451a1740f2eda6c14 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromProductPageTest.xml @@ -27,7 +27,7 @@ </data> <data name="payment/method" xsi:type="string">paypal_express</data> <data name="configData" xsi:type="string">paypal_express, freeshipping</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.php index 7fc35ff1eb6fd83e929886bcdeac741de868fe68..6ecf356e27ff8c61a04329cb3211f0f6e1e9c05f 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.php @@ -31,6 +31,7 @@ class ExpressCheckoutFromShoppingCartTest extends Scenario const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; const TO_MAINTAIN = 'yes'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml index e4a4cd484a45a3b0ed0d8e015d791c207563b96a..38c876719bc3d82c772ff6f86cd507229ec5cf2b 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutFromShoppingCartTest.xml @@ -28,7 +28,7 @@ <item name="grandTotal" xsi:type="string">145.98</item> </data> <data name="configData" xsi:type="string">payflowpro</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.php index b40adf986dbe9a7311fec484411bb8a7ed50810d..5d7056d32703dbe6b79c6b099ab9a11fa2b9d41f 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.php @@ -35,6 +35,7 @@ class ExpressCheckoutOnePageTest extends Scenario const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; const TO_MAINTAIN = 'yes'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml index df02c3293b50c5ed1bb2394bdeb6fc378f677874..e52ed631ae46f983dc8860e7bf628f95a5a6a7fb 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ExpressCheckoutOnePageTest.xml @@ -26,7 +26,7 @@ </data> <data name="payment/method" xsi:type="string">paypal_express</data> <data name="configData" xsi:type="string">paypal_express</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> @@ -51,7 +51,7 @@ </data> <data name="payment/method" xsi:type="string">paypal_express</data> <data name="configData" xsi:type="string">payflowlink</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.php index 07f03b1c931cfc0c547f781451630fa828101294..66ca21d8d0f3eae424d9b3f926bf0003ff3dd9d6 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.php @@ -32,6 +32,7 @@ class InContextExpressCheckoutFromShoppingCartTest extends Scenario const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; const TO_MAINTAIN = 'yes'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.xml index 979a8c2f32db3fd9333adbfe6fd8b52263edf462..bbc566d1e31ec620746316d2e3ebcfe76c6883ae 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressCheckoutFromShoppingCartTest.xml @@ -10,7 +10,7 @@ <variation name="InContextExpressCheckoutFromShoppingCartTestVariation1" summary="In-Context Checkout with PayPal Express Checkout (API Credentials) from shopping cart " ticketId="MAGETWO-47213"> <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> <data name="configData" xsi:type="string">paypal_express, paypal_express_in_context</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Paypal\Test\Constraint\AssertExpressCancelledMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.php index ff06e6b10266ef4b723811bf143b648aa59853e9..b96f6a3e4a70a4ce320b0bf7b424675a0eee8fc4 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.php @@ -32,6 +32,7 @@ class InContextExpressOnePageCheckoutTest extends Scenario const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; const TO_MAINTAIN = 'yes'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.xml index cbdd9c3b6d6d31d13acac40ef5c8420b51054f85..5fa8e6e8b775dfb4e0bae646d431196823312f98 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/InContextExpressOnePageCheckoutTest.xml @@ -18,7 +18,7 @@ </data> <data name="payment/method" xsi:type="string">paypal_express</data> <data name="configData" xsi:type="string">paypal_express_in_context, paypal_express, freeshipping</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Paypal\Test\Constraint\AssertExpressCancelledMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..379e90eb72c0d15c28b4de340f019ed21e9978c5 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/NavigateMenuTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate through admin menu" ticketId="MAGETWO-34874"> + <variation name="NavigateMenuTest50"> + <data name="tag" xsi:type="string">severity:S0</data> + <data name="menuItem" xsi:type="string">Reports > PayPal Settlement</data> + <data name="pageTitle" xsi:type="string">PayPal Settlement Reports</data> + <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> + </variation> + <variation name="NavigateMenuTest51"> + <data name="tag" xsi:type="string">severity:S0</data> + <data name="menuItem" xsi:type="string">Sales > Billing Agreements</data> + <data name="pageTitle" xsi:type="string">Billing Agreements</data> + <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml index bc3ee3cef1c68ba1668891cce3e6f2eb45a4d86d..c33abb1dbdd2263fded6dbbfb0f683848a3bdd3f 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml @@ -23,7 +23,7 @@ <data name="creditCard/dataset" xsi:type="string">visa_default</data> <data name="configData" xsi:type="string">payflowpro, payflowpro_use_vault</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" /> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/UseVaultOnCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/UseVaultOnCheckoutTest.xml index bde3837d71fda02bad93ac1ced7c3c08835deafe..92b4be03b5937ffa318f9d3cd05f6e6d2290f6bc 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/UseVaultOnCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/UseVaultOnCheckoutTest.xml @@ -21,7 +21,7 @@ <data name="creditCardSave" xsi:type="string">Yes</data> <data name="configData" xsi:type="string">payflowpro, payflowpro_use_vault</data> <data name="status" xsi:type="string">Processing</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <data name="status" xsi:type="string">Processing</data> <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Email, Hold, Invoice, Ship, Reorder, Edit</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..e56386d5e0eacc346bfb5ddaf84319a1d1d16313 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. 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\Paypal\Test\Constraint\Sandbox\AssertTotalPaypalReview"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Paypal\Test\Constraint\AssertExpressCancelledMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/testcase.xml index f361530def8617d5e4d13451b24e329d31794ef2..89cf35b73bc3ed292a152358fd5265a71d566983 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/etc/testcase.xml @@ -69,4 +69,20 @@ <step name="checkPayflowProConfig" module="Magento_Paypal" next="checkPayflowLinkConfig" /> <step name="checkPayflowLinkConfig" module="Magento_Paypal" /> </scenario> + <scenario name="CreatePayFlowOrderBackendNegativeTest" firstStep="setupConfiguration"> + <step name="setupConfiguration" module="Magento_Config" next="createProducts" /> + <step name="createProducts" module="Magento_Catalog" next="createCustomer" /> + <step name="createCustomer" module="Magento_Customer" next="openSalesOrders" /> + <step name="openSalesOrders" module="Magento_Sales" next="createNewOrder" /> + <step name="createNewOrder" module="Magento_Sales" next="selectCustomerOrder" /> + <step name="selectCustomerOrder" module="Magento_Sales" next="selectStore" /> + <step name="selectStore" module="Magento_Sales" next="addProducts" /> + <step name="addProducts" module="Magento_Sales" next="fillAccountInformation" /> + <step name="fillAccountInformation" module="Magento_Sales" next="fillBillingAddress" /> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> + <step name="selectShippingMethodForOrder" module="Magento_Sales" next="saveCreditCardOnBackend" /> + <step name="saveCreditCardOnBackend" module="Magento_Vault" next="submitOrderNegative" /> + <step name="submitOrderNegative" module="Magento_Sales" /> + </scenario> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php index 874fe2844c476cd530b682143b3099c41646e28d..c25b96a624bfda79865d81f73f3208f0bc38d74a 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create.php @@ -165,7 +165,7 @@ class Create extends Block * * @return \Magento\Sales\Test\Block\Adminhtml\Order\Create\Billing\Method */ - protected function getBillingMethodBlock() + public function getBillingMethodBlock() { return $this->blockFactory->create( \Magento\Sales\Test\Block\Adminhtml\Order\Create\Billing\Method::class, @@ -279,20 +279,22 @@ class Create extends Block } /** - * Fill addresses based on present data in customer and order fixtures. + * Fill Billing Address. * - * @param FixtureInterface $address - * @param string $saveAddress + * @param FixtureInterface $billingAddress + * @param string $saveAddress [optional] * @param bool $setShippingAddress [optional] * @return void */ - public function fillAddresses(FixtureInterface $address, $saveAddress = 'No', $setShippingAddress = true) - { - if ($setShippingAddress) { + public function fillBillingAddress( + FixtureInterface $billingAddress, + $saveAddress = 'No', + $setShippingAddress = true + ) { + if ($setShippingAddress !== false) { $this->getShippingAddressBlock()->uncheckSameAsBillingShippingAddress(); } - $this->browser->find($this->header)->hover(); - $this->getBillingAddressBlock()->fill($address); + $this->getBillingAddressBlock()->fill($billingAddress); $this->getBillingAddressBlock()->saveInAddressBookBillingAddress($saveAddress); $this->getTemplateBlock()->waitLoader(); if ($setShippingAddress) { @@ -301,6 +303,18 @@ class Create extends Block } } + /** + * Fill Shipping Address. + * + * @param FixtureInterface $shippingAddress + * @return void + */ + public function fillShippingAddress(FixtureInterface $shippingAddress) + { + $this->getShippingAddressBlock()->fill($shippingAddress); + $this->getTemplateBlock()->waitLoader(); + } + /** * Select shipping method. * diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Billing/Method.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Billing/Method.php index 280211c7ef20f54e43f31227551a2b832d7c1ea6..8477af0087fd47e07b8fee3e6c5138c690369d05 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Billing/Method.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Billing/Method.php @@ -8,6 +8,7 @@ namespace Magento\Sales\Test\Block\Adminhtml\Order\Create\Billing; use Magento\Mtf\Block\Block; use Magento\Mtf\Fixture\InjectableFixture; +use Magento\Mtf\Client\Locator; /** * Adminhtml sales order create payment method block. @@ -42,6 +43,20 @@ class Method extends Block */ protected $loader = '[data-role=loader]'; + /** + * Field with Mage error. + * + * @var string + */ + protected $mageErrorField = '//fieldset/*[contains(@class,"field ")][.//*[contains(@class,"error")]]'; + + /** + * Mage error text. + * + * @var string + */ + protected $mageErrorText = './/label[contains(@class,"error")]'; + /** * Select payment method. * @@ -69,4 +84,23 @@ class Method extends Block $formBlock->fill($creditCard); } } + + /** + * @return array + */ + public function getJsErrors() + { + $data = []; + $elements = $this->_rootElement->getElements($this->mageErrorField, Locator::SELECTOR_XPATH); + foreach ($elements as $element) { + $error = $element->find($this->mageErrorText, Locator::SELECTOR_XPATH); + if ($error->isVisible()) { + $label = $element->find('.//*[contains(@name,"payment")]', Locator::SELECTOR_XPATH); + $label = $label->getAttribute('name'); + $label = preg_replace('/payment\[(.*)\]/u', '$1', $label); + $data[$label] = $error->getText(); + } + } + return $data; + } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.php index 044286ea1434a369b31074b739b8ee998b405c8f..f52d898d232d145c117911d77968431001eeba70 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.php @@ -9,6 +9,7 @@ namespace Magento\Sales\Test\Block\Adminhtml\Order\Create\Shipping; use Magento\Mtf\Block\Form; use Magento\Mtf\Client\Locator; use Magento\Backend\Test\Block\Template; +use Magento\Mtf\Client\Element\SimpleElement; /** * Adminhtml sales order create shipping address block. @@ -29,6 +30,13 @@ class Address extends Form */ protected $sameAsBilling = '#order-shipping_same_as_billing'; + /** + * Wait element. + * + * @var string + */ + private $waitElement = '.loading-mask'; + /** * Shipping address title selector. * @@ -95,4 +103,41 @@ class Address extends Form ['element' => $this->_rootElement->find($this->templateBlock, Locator::SELECTOR_XPATH)] ); } + + /** + * Fill specified form data. + * + * @param array $fields + * @param SimpleElement $element + * @return void + * @throws \Exception + */ + protected function _fill(array $fields, SimpleElement $element = null) + { + $context = ($element === null) ? $this->_rootElement : $element; + foreach ($fields as $name => $field) { + $this->waitFormLoading(); + $element = $this->getElement($context, $field); + if (!$element->isDisabled()) { + $element->setValue($field['value']); + } else { + throw new \Exception("Unable to set value to field '$name' as it's disabled."); + } + } + } + + /** + * Wait for form loading. + * + * @return void + */ + private function waitFormLoading() + { + $this->_rootElement->click(); + $this->browser->waitUntil( + function () { + return $this->browser->find($this->waitElement)->isVisible() ? null : true; + } + ); + } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.xml new file mode 100644 index 0000000000000000000000000000000000000000..2dc30a686c0d9c3b997f49a2998fff971b6b1b64 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Address.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping strict="0"> + <wrapper>order[shipping_address]</wrapper> + <fields> + <firstname /> + <lastname /> + <company /> + <street> + <selector>[name='order[shipping_address][street][0]']</selector> + </street> + <city /> + <country_id> + <input>select</input> + </country_id> + <region_id> + <input>select</input> + </region_id> + <postcode /> + <telephone /> + </fields> +</mapping> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Method.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Method.php index 3d9673e29805dee1383ac9b3354c4905886748e8..90a75f665cbf9f95eebe2dfa26d135082d3912f4 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Method.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Create/Shipping/Method.php @@ -28,6 +28,13 @@ class Method extends Block */ protected $shippingMethod = '//dt[contains(.,"%s")]/following-sibling::*//*[contains(text(), "%s")]'; + /** + * Wait element. + * + * @var string + */ + private $waitElement = '.loading-mask'; + /** * Select shipping method. * @@ -36,6 +43,7 @@ class Method extends Block */ public function selectShippingMethod(array $shippingMethod) { + $this->waitFormLoading(); if ($this->_rootElement->find($this->shippingMethodsLink)->isVisible()) { $this->_rootElement->find($this->shippingMethodsLink)->click(); } @@ -46,4 +54,19 @@ class Method extends Block ); $this->_rootElement->find($selector, Locator::SELECTOR_XPATH)->click(); } + + /** + * Wait for form loading. + * + * @return void + */ + private function waitFormLoading() + { + $this->_rootElement->click(); + $this->browser->waitUntil( + function () { + return $this->browser->find($this->waitElement)->isVisible() ? null : true; + } + ); + } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.php index 1779ad437c76e4192fd8c9774fc5a6f0ab8ccafb..71575ffb16b858cd43353c02d82f2d496eac4a51 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.php @@ -10,12 +10,13 @@ use Magento\Sales\Test\Fixture\OrderInjectable; use Magento\Sales\Test\Page\Adminhtml\OrderIndex; use Magento\Sales\Test\Page\Adminhtml\SalesOrderView; use Magento\Mtf\TestCase\Injectable; +use Magento\Mtf\TestStep\TestStepFactory; /** * Preconditions: - * 1. Enable payment method "Check/Money Order". - * 2. Enable shipping method one of "Flat Rate". - * 3. Create order + * 1. Enable payment method: "Check/Money Order/Bank Transfer/Cash on Delivery/Purchase Order/Zero Subtotal Checkout". + * 2. Enable shipping method one of "Flat Rate/Free Shipping". + * 3. Create order. * * Steps: * 1. Login to backend. @@ -48,20 +49,7 @@ class CancelCreatedOrderTest extends Injectable protected $salesOrderView; /** - * Enable "Check/Money Order" and "Flat Rate" in configuration. - * - * @return void - */ - public function __prepare() - { - $this->objectManager->create( - \Magento\Config\Test\TestStep\SetupConfigurationStep::class, - ['configData' => 'checkmo, flatrate', 'rollback' => true] - )->run(); - } - - /** - * Inject pages + * Inject pages. * * @param OrderIndex $orderIndex * @param SalesOrderView $salesOrderView @@ -77,11 +65,17 @@ class CancelCreatedOrderTest extends Injectable * Cancel created order. * * @param OrderInjectable $order + * @param TestStepFactory $stepFactory + * @param string $configData * @return array */ - public function test(OrderInjectable $order) + public function test(OrderInjectable $order, TestStepFactory $stepFactory, $configData) { // Preconditions + $stepFactory->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $configData] + )->run(); $order->persist(); // Steps diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml index bcacacc57589a88806a2bcad18912ab84760ec8f..b65b9a31f72f3d7ff7290afb4393edeaf73679bc 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CancelCreatedOrderTest.xml @@ -7,14 +7,49 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\CancelCreatedOrderTest" summary="Cancel Created Order for Offline Payment Methods" ticketId="MAGETWO-28191"> - <variation name="CancelCreatedOrderTestVariation1"> - <data name="description" xsi:type="string">cancel order and check status on storefront</data> + <variation name="CancelCreatedOrderTestVariationWithCheckMoneyOrderPaymentMethod" summary="Cancel order with check/money order payment method and check status on storefront"> <data name="order/dataset" xsi:type="string">default</data> <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::default,catalogProductSimple::default</data> <data name="status" xsi:type="string">Canceled</data> + <data name="configData" xsi:type="string">checkmo</data> <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelSuccessMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> </variation> + <variation name="CancelCreatedOrderTestVariationWithZeroSubtotalCheckout" summary="Cancel order with zero subtotal checkout payment method and check status on storefront"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">free</data> + <data name="order/data/shipping_method" xsi:type="string">freeshipping_freeshipping</data> + <data name="order/data/coupon_code/dataset" xsi:type="string">active_sales_rule_with_fixed_price_discount_coupon</data> + <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::product_10_dollar</data> + <data name="status" xsi:type="string">Canceled</data> + <data name="configData" xsi:type="string">zero_subtotal_checkout, freeshipping</data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelSuccessMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> + </variation> + <variation name="CancelCreatedOrderTestVariationWithBankTransferPaymentMethod" summary="Cancel order with bank transfer payment method and check status on storefront"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">banktransfer</data> + <data name="status" xsi:type="string">Canceled</data> + <data name="configData" xsi:type="string">banktransfer</data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelSuccessMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> + </variation> + <variation name="CancelCreatedOrderTestVariationWithCashOnDeliveryPaymentMethod" summary="Cancel order with cash on delivery payment method and check status on storefront"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">cashondelivery</data> + <data name="status" xsi:type="string">Canceled</data> + <data name="configData" xsi:type="string">cashondelivery</data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelSuccessMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> + </variation> + <variation name="CancelCreatedOrderTestVariationWithPurchaseOrderPaymentMethod" summary="Cancel order with purchase order payment method and check status on storefront"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">purchaseorder</data> + <data name="status" xsi:type="string">Canceled</data> + <data name="configData" xsi:type="string">purchaseorder</data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelSuccessMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php index ed00a414bd0e689cfdb15b75ccf5d795ca40c992..9d19d10f4d40c6c3e317a97e7e83cd3c79e6f8ae 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.php @@ -10,10 +10,11 @@ use Magento\Sales\Test\Fixture\OrderInjectable; use Magento\Mtf\Fixture\FixtureFactory; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\TestCase\Injectable; +use Magento\Mtf\TestStep\TestStepFactory; /** * Preconditions: - * 1. Enable payment method "Check/Money Order". + * 1. Enable payment method one of "Check/Money Order/Bank Transfer/Cash on Delivery/Purchase Order". * 2. Enable shipping method one of "Flat Rate/Free Shipping". * 3. Create order. * 4. Create Invoice. @@ -54,38 +55,34 @@ class CreateCreditMemoEntityTest extends Injectable 'price', ]; - /** - * Set up configuration. - * - * @param FixtureFactory $fixtureFactory - * @return void - */ - public function __prepare(FixtureFactory $fixtureFactory) - { - $this->fixtureFactory = $fixtureFactory; - - $setupConfigurationStep = $this->objectManager->create( - \Magento\Config\Test\TestStep\SetupConfigurationStep::class, - ['configData' => 'checkmo, flatrate'] - ); - $setupConfigurationStep->run(); - } - /** * Create credit memo. * + * @param TestStepFactory $stepFactory + * @param FixtureFactory $fixtureFactory * @param OrderInjectable $order * @param array $data + * @param string $configData * @return array */ - public function test(OrderInjectable $order, array $data) - { + public function test( + TestStepFactory $stepFactory, + FixtureFactory $fixtureFactory, + OrderInjectable $order, + array $data, + $configData + ) { // Preconditions + $this->fixtureFactory = $fixtureFactory; + $stepFactory->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $configData] + )->run(); $order->persist(); - $this->objectManager->create(\Magento\Sales\Test\TestStep\CreateInvoiceStep::class, ['order' => $order])->run(); + $stepFactory->create(\Magento\Sales\Test\TestStep\CreateInvoiceStep::class, ['order' => $order])->run(); // Steps - $createCreditMemoStep = $this->objectManager->create( + $createCreditMemoStep = $stepFactory->create( \Magento\Sales\Test\TestStep\CreateCreditMemoStep::class, ['order' => $order, 'data' => $data] ); diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml index 80e1bfdf81e8f39b5547fd96def138063fa00231..02b9640acbea4159c0560e8a4727a13cc36e1199 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCreditMemoEntityTest.xml @@ -7,14 +7,14 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\CreateCreditMemoEntityTest" summary="Create Credit Memo for Offline Payment Methods" ticketId="MAGETWO-29116"> - <variation name="CreateCreditMemoEntityTestVariation1"> - <data name="description" xsi:type="string">Assert items return to stock (partial refund)</data> + <variation name="CreateCreditMemoEntityTestVariation1" summary="Assert items return to stock (partial refund)"> <data name="data/items_data/0/back_to_stock" xsi:type="string">Yes</data> <data name="data/items_data/0/qty" xsi:type="string">1</data> <data name="data/form_data/send_email" xsi:type="string">Yes</data> <data name="order/dataset" xsi:type="string">default</data> <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::product_100_dollar</data> <data name="order/data/price/dataset" xsi:type="string">partial_refund</data> + <data name="configData" xsi:type="string">checkmo</data> <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertCreditMemoButton" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCreditMemoTab" /> @@ -25,15 +25,15 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> <constraint name="Magento\Sales\Test\Constraint\AssertCreditMemoItems" /> </variation> - <variation name="CreateCreditMemoEntityTestVariation2"> - <data name="description" xsi:type="string">Assert 0 shipping refund</data> + <variation name="CreateCreditMemoEntityTestVariation2" summary="Assert 0 shipping refund"> <data name="data/items_data/0/qty" xsi:type="string">1</data> <data name="data/form_data/shipping_amount" xsi:type="string">0</data> <data name="data/form_data/adjustment_positive" xsi:type="string">5</data> <data name="data/form_data/adjustment_negative" xsi:type="string">10</data> <data name="order/dataset" xsi:type="string">default</data> - <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">banktransfer</data> <data name="order/data/price/dataset" xsi:type="string">full_refund_with_zero_shipping_refund</data> + <data name="configData" xsi:type="string">banktransfer</data> <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertCreditMemoButton" /> <constraint name="Magento\Sales\Test\Constraint\AssertRefundInCreditMemoTab" /> @@ -41,5 +41,29 @@ <constraint name="Magento\Sales\Test\Constraint\AssertRefundedGrandTotalOnFrontend" /> <constraint name="Magento\Sales\Test\Constraint\AssertCreditMemoItems" /> </variation> + <variation name="CreateCreditMemoEntityTestVariationWithCashOnDeliveryPaymentMethod" summary="Assert 0 shipping refund with Cash on delivery payment method"> + <data name="data/items_data/0/qty" xsi:type="string">1</data> + <data name="data/form_data/shipping_amount" xsi:type="string">0</data> + <data name="data/form_data/adjustment_positive" xsi:type="string">5</data> + <data name="data/form_data/adjustment_negative" xsi:type="string">10</data> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">cashondelivery</data> + <data name="order/data/price/dataset" xsi:type="string">full_refund_with_zero_shipping_refund</data> + <data name="configData" xsi:type="string">cashondelivery</data> + <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertRefundedGrandTotalOnFrontend" /> + </variation> + <variation name="CreateCreditMemoEntityTestVariationWithPurchaseOrderPaymentMethod" summary="Assert 0 shipping refund with Purchase Order payment method"> + <data name="data/items_data/0/qty" xsi:type="string">1</data> + <data name="data/form_data/shipping_amount" xsi:type="string">0</data> + <data name="data/form_data/adjustment_positive" xsi:type="string">5</data> + <data name="data/form_data/adjustment_negative" xsi:type="string">10</data> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">purchaseorder</data> + <data name="order/data/price/dataset" xsi:type="string">full_refund_with_zero_shipping_refund</data> + <data name="configData" xsi:type="string">purchaseorder</data> + <constraint name="Magento\Sales\Test\Constraint\AssertRefundSuccessCreateMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertRefundedGrandTotalOnFrontend" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.php index f62583583c3dc565579196d24ed564a14613eef9..f7cdaf74ab0f57e0544a392f6a936498bcb8efee 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.php @@ -8,10 +8,11 @@ namespace Magento\Sales\Test\TestCase; use Magento\Sales\Test\Fixture\OrderInjectable; use Magento\Mtf\TestCase\Injectable; +use Magento\Mtf\TestStep\TestStepFactory; /** * Preconditions: - * 1. Enable payment method "Check/Money Order". + * 1. Enable payment method: "Check/Money Order/Bank Transfer/Cash on Delivery/Purchase Order/Zero Subtotal Checkout". * 2. Enable shipping method one of "Flat Rate/Free Shipping". * 3. Create order. * @@ -33,16 +34,21 @@ class CreateInvoiceEntityTest extends Injectable /* end tags */ /** - * Set up configuration. + * Factory for Test Steps. * + * @var TestStepFactory + */ + protected $stepFactory; + + /** + * Prepare data. + * + * @param TestStepFactory $stepFactory * @return void */ - public function __prepare() + public function __prepare(TestStepFactory $stepFactory) { - $this->objectManager->create( - \Magento\Config\Test\TestStep\SetupConfigurationStep::class, - ['configData' => 'checkmo, flatrate'] - )->run(); + $this->stepFactory = $stepFactory; } /** @@ -50,15 +56,20 @@ class CreateInvoiceEntityTest extends Injectable * * @param OrderInjectable $order * @param array $data + * @param string $configData * @return array */ - public function test(OrderInjectable $order, array $data) + public function test(OrderInjectable $order, array $data, $configData) { // Preconditions + $this->stepFactory->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $configData] + )->run(); $order->persist(); // Steps - $result = $this->objectManager->create( + $result = $this->stepFactory->create( \Magento\Sales\Test\TestStep\CreateInvoiceStep::class, ['order' => $order, 'data' => $data] )->run(); @@ -73,6 +84,6 @@ class CreateInvoiceEntityTest extends Injectable */ public function tearDown() { - $this->objectManager->create(\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep::class)->run(); + $this->stepFactory->create(\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep::class)->run(); } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.xml index afc4bad5e706db538cd5957201a4a5c9347a3605..8753de91d1f4301710f15a968b7a7ea24e04d663 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateInvoiceEntityTest.xml @@ -10,11 +10,11 @@ <variation name="CreateInvoiceEntityTestVariation1"> <data name="order/dataset" xsi:type="string">default</data> <data name="order/data/price/dataset" xsi:type="string">full_invoice</data> - <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::default</data> <data name="order/data/total_qty_ordered/0" xsi:type="string">1</data> <data name="data/items_data/0/qty" xsi:type="string">-</data> <data name="data/form_data/do_shipment" xsi:type="string">Yes</data> <data name="data/form_data/comment_text" xsi:type="string">comments</data> + <data name="configData" xsi:type="string">checkmo</data> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceWithShipmentSuccessMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertNoInvoiceButton" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceInInvoicesTab" /> @@ -29,14 +29,57 @@ <data name="order/data/price/dataset" xsi:type="string">partial_invoice</data> <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::product_100_dollar</data> <data name="order/data/total_qty_ordered/0" xsi:type="string">-</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">banktransfer</data> <data name="data/items_data/0/qty" xsi:type="string">1</data> <data name="data/form_data/do_shipment" xsi:type="string">No</data> <data name="data/form_data/comment_text" xsi:type="string">comments</data> + <data name="configData" xsi:type="string">banktransfer</data> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceInInvoicesTab" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceInInvoicesGrid" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceItems" /> <constraint name="Magento\Sales\Test\Constraint\AssertInvoicedAmountOnFrontend" /> </variation> + <variation name="CreateInvoiceEntityTestVariationWithCashOnDeliveryPaymentMethod"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/price/dataset" xsi:type="string">partial_invoice</data> + <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::product_100_dollar</data> + <data name="order/data/total_qty_ordered/0" xsi:type="string">-</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">cashondelivery</data> + <data name="data/items_data/0/qty" xsi:type="string">1</data> + <data name="data/form_data/do_shipment" xsi:type="string">No</data> + <data name="data/form_data/comment_text" xsi:type="string">comments</data> + <data name="configData" xsi:type="string">cashondelivery</data> + <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertInvoicedAmountOnFrontend" /> + </variation> + <variation name="CreateInvoiceEntityTestVariationWithPurchaseOrderPaymentMethod"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/price/dataset" xsi:type="string">partial_invoice</data> + <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::product_100_dollar</data> + <data name="order/data/total_qty_ordered/0" xsi:type="string">-</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">purchaseorder</data> + <data name="data/items_data/0/qty" xsi:type="string">1</data> + <data name="data/form_data/do_shipment" xsi:type="string">No</data> + <data name="data/form_data/comment_text" xsi:type="string">comments</data> + <data name="configData" xsi:type="string">purchaseorder</data> + <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertInvoicedAmountOnFrontend" /> + </variation> + <variation name="CreateInvoiceEntityTestVariationWithZeroSubtotalCheckout"> + <data name="order/dataset" xsi:type="string">default</data> + <data name="order/data/price/dataset" xsi:type="string">partial_invoice</data> + <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::product_10_dollar</data> + <data name="order/data/total_qty_ordered/0" xsi:type="string">-</data> + <data name="order/data/payment_auth_expiration/method" xsi:type="string">free</data> + <data name="order/data/shipping_method" xsi:type="string">freeshipping_freeshipping</data> + <data name="order/data/coupon_code/dataset" xsi:type="string">active_sales_rule_with_fixed_price_discount_coupon</data> + <data name="data/items_data/0/qty" xsi:type="string">1</data> + <data name="data/form_data/do_shipment" xsi:type="string">No</data> + <data name="data/form_data/comment_text" xsi:type="string">comments</data> + <data name="configData" xsi:type="string">zero_subtotal_checkout, freeshipping</data> + <constraint name="Magento\Sales\Test\Constraint\AssertInvoiceSuccessCreateMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertInvoicedAmountOnFrontend" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillBillingAddressStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillBillingAddressStep.php index bde5bff6de9d4e1f53969137ac86c94d8323c7fd..4d8321e1d9b2bd04780a06b1a61621b34aae387b 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillBillingAddressStep.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillBillingAddressStep.php @@ -11,7 +11,7 @@ use Magento\Sales\Test\Page\Adminhtml\OrderCreateIndex; use Magento\Mtf\TestStep\TestStepInterface; /** - * Fill Sales Data. + * Fill Billing Address. */ class FillBillingAddressStep implements TestStepInterface { @@ -23,12 +23,19 @@ class FillBillingAddressStep implements TestStepInterface protected $orderCreateIndex; /** - * Address. + * Billing Address fixture. * * @var Address */ protected $billingAddress; + /** + * Shipping Address fixture. + * + * @var Address + */ + protected $shippingAddress; + /** * Save Address. * @@ -37,40 +44,45 @@ class FillBillingAddressStep implements TestStepInterface protected $saveAddress; /** - * Flag for set same as billing shipping address. + * Flag to set 'Same as billing address' for shipping address. * * @var string */ protected $setShippingAddress; /** - * @constructor * @param OrderCreateIndex $orderCreateIndex * @param Address $billingAddress - * @param string $saveAddress + * @param Address $shippingAddress [optional] + * @param string $saveAddress [optional] * @param bool $setShippingAddress [optional] */ public function __construct( OrderCreateIndex $orderCreateIndex, Address $billingAddress, + Address $shippingAddress = null, $saveAddress = 'No', $setShippingAddress = true ) { $this->orderCreateIndex = $orderCreateIndex; $this->billingAddress = $billingAddress; + $this->shippingAddress = $shippingAddress; $this->saveAddress = $saveAddress; $this->setShippingAddress = $setShippingAddress; } /** - * Fill Sales Data. + * Fill Billing Address. * - * @return Address + * @return array */ public function run() { + if ($this->shippingAddress !== null) { + $this->setShippingAddress = null; + } $this->orderCreateIndex->getCreateBlock() - ->fillAddresses($this->billingAddress, $this->saveAddress, $this->setShippingAddress); + ->fillBillingAddress($this->billingAddress, $this->saveAddress, $this->setShippingAddress); return ['billingAddress' => $this->billingAddress]; } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillShippingAddressStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillShippingAddressStep.php new file mode 100644 index 0000000000000000000000000000000000000000..a9d6433c6695830cee27a4589c401b3d9dcf4a34 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/FillShippingAddressStep.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Sales\Test\TestStep; + +use Magento\Customer\Test\Fixture\Address; +use Magento\Sales\Test\Page\Adminhtml\OrderCreateIndex; +use Magento\Mtf\TestStep\TestStepInterface; + +/** + * Fill Shipping Address. + */ +class FillShippingAddressStep implements TestStepInterface +{ + /** + * Sales order create index page. + * + * @var OrderCreateIndex + */ + protected $orderCreateIndex; + + /** + * Shipping Address fixture. + * + * @var Address + */ + protected $shippingAddress; + + /** + * @param OrderCreateIndex $orderCreateIndex + * @param Address $shippingAddress [optional] + */ + public function __construct( + OrderCreateIndex $orderCreateIndex, + Address $shippingAddress = null + ) { + $this->orderCreateIndex = $orderCreateIndex; + $this->shippingAddress = $shippingAddress; + } + + /** + * Fill Shipping Address. + * + * @return void + */ + public function run() + { + if ($this->shippingAddress !== null) { + $this->orderCreateIndex->getCreateBlock()->fillShippingAddress($this->shippingAddress); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderNegativeStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderNegativeStep.php new file mode 100644 index 0000000000000000000000000000000000000000..ece554eee9c9c32fdc94c74c48589e804aa9090f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderNegativeStep.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Sales\Test\TestStep; + +use Magento\Sales\Test\Page\Adminhtml\OrderCreateIndex; +use Magento\Mtf\TestStep\TestStepInterface; + +/** + * Submit Order step. + */ +class SubmitOrderNegativeStep implements TestStepInterface +{ + /** + * Sales order create index page. + * + * @var OrderCreateIndex + */ + protected $orderCreateIndex; + + /** + * @constructor + * @param OrderCreateIndex $orderCreateIndex + */ + public function __construct( + OrderCreateIndex $orderCreateIndex + ) { + $this->orderCreateIndex = $orderCreateIndex; + } + + /** + * Fill Sales Data. + * + * @return void + */ + public function run() + { + $this->orderCreateIndex->getCreateBlock()->submitOrder(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml index c48430b00e7ea4412265dfaf4d91ed85cbd2819c..eebdf2128dc70e8dc90a0867040e1e3682462768 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/testcase.xml @@ -11,7 +11,8 @@ <step name="createOrder" module="Magento_Sales" next="openOrder" /> <step name="openOrder" module="Magento_Sales" next="reorder" /> <step name="reorder" module="Magento_Sales" next="fillBillingAddress" /> - <step name="fillBillingAddress" module="Magento_Sales" next="selectPaymentMethodForOrder" /> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Sales" next="selectPaymentMethodForOrder" /> <step name="selectPaymentMethodForOrder" module="Magento_Sales" next="selectShippingMethodForOrder" /> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="submitOrder" /> <step name="submitOrder" module="Magento_Sales" /> @@ -28,7 +29,8 @@ <step name="addProducts" module="Magento_Sales" next="updateProductsData" /> <step name="updateProductsData" module="Magento_Sales" next="fillAccountInformation" /> <step name="fillAccountInformation" module="Magento_Sales" next="fillBillingAddress" /> - <step name="fillBillingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="selectPaymentMethodForOrder" /> <step name="selectPaymentMethodForOrder" module="Magento_Sales" next="submitOrder" /> <step name="submitOrder" module="Magento_Sales" /> @@ -41,7 +43,8 @@ <step name="selectCustomerOrder" module="Magento_Sales" next="selectStore" /> <step name="selectStore" module="Magento_Sales" next="addProducts" /> <step name="addProducts" module="Magento_Sales" next="fillBillingAddress" /> - <step name="fillBillingAddress" module="Magento_Sales" next="selectPaymentMethodForOrder" /> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Sales" next="selectPaymentMethodForOrder" /> <step name="selectPaymentMethodForOrder" module="Magento_Sales" next="selectShippingMethodForOrder" /> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="submitOrder" /> <step name="submitOrder" module="Magento_Sales" next="openSalesOrderOnFrontendForGuest" /> @@ -60,7 +63,8 @@ <step name="addProducts" module="Magento_Sales" next="updateProductsData" /> <step name="updateProductsData" module="Magento_Sales" next="fillAccountInformation" /> <step name="fillAccountInformation" module="Magento_Sales" next="fillBillingAddress" /> - <step name="fillBillingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="selectPaymentMethodForOrder" /> <step name="selectPaymentMethodForOrder" module="Magento_Sales" next="submitOrder" /> <step name="submitOrder" module="Magento_Sales" next="createInvoice" /> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertCustomerEmailChanged.php b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertCustomerEmailChanged.php new file mode 100644 index 0000000000000000000000000000000000000000..68384aacbc70cc4759f99ab9cb0d1cb42ac2ca9d --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertCustomerEmailChanged.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\Constraint; + +use Magento\Customer\Test\Fixture\Customer; +use Magento\Customer\Test\Page\CustomerAccountIndex; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Mtf\Fixture\FixtureFactory; + +/** + * Check that login again to frontend with new email was successful. + */ +class AssertCustomerEmailChanged extends AbstractConstraint +{ + /** + * Assert that login again to frontend with new email was successful. + * + * @param FixtureFactory $fixtureFactory + * @param CustomerAccountIndex $customerAccountIndex + * @param Customer $initialCustomer + * @param Customer $customer + * @return void + */ + public function processAssert( + FixtureFactory $fixtureFactory, + CustomerAccountIndex $customerAccountIndex, + Customer $initialCustomer, + Customer $customer + ) { + $customer = $fixtureFactory->createByCode( + 'customer', + [ + 'data' => [ + 'email' => $customer->getEmail(), + 'password' => $initialCustomer->getPassword() + ], + ] + ); + + $this->objectManager->create( + \Magento\Customer\Test\TestStep\LoginCustomerOnFrontendStep::class, + ['customer' => $customer] + )->run(); + + \PHPUnit_Framework_Assert::assertTrue( + $customerAccountIndex->getAccountMenuBlock()->isVisible(), + 'Customer Account Dashboard is not visible.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Customer email was changed.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertDefaultAccountInformation.php b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertDefaultAccountInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..642db5925157414db3220aceae8049af209ed2d6 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/Constraint/AssertDefaultAccountInformation.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\Constraint; + +use Magento\Customer\Test\Page\CustomerAccountEdit; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert fields visibility in customer account information tab. + */ +class AssertDefaultAccountInformation extends AbstractConstraint +{ + /** + * Assert fields visibility in customer account information tab. + * + * @param CustomerAccountEdit $customerAccountEdit + * @return void + */ + public function processAssert( + CustomerAccountEdit $customerAccountEdit + ) { + $infoForm = $customerAccountEdit->getAccountInfoForm(); + + \PHPUnit_Framework_Assert::assertFalse( + $infoForm->isEmailVisible(), + 'Email text field should not be visible.' + ); + + \PHPUnit_Framework_Assert::assertFalse( + $infoForm->isCurrentPasswordVisible(), + 'Current Password text field should not be visible.' + ); + + \PHPUnit_Framework_Assert::assertFalse( + $infoForm->isPasswordVisible(), + 'New Password text field should not be visible.' + ); + + \PHPUnit_Framework_Assert::assertFalse( + $infoForm->isConfirmPasswordVisible(), + 'Password Confirmation text field should not be visible.' + ); + } + + /** + * String representation of success assert. + * + * @return string + */ + public function toString() + { + return 'Default customer account information tab is correct.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerEmailTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerEmailTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9f1acd69d92a2e3ba02e52ad4e563932924f2305 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerEmailTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\TestCase; + +use Magento\Mtf\TestCase\Injectable; +use Magento\Customer\Test\Fixture\Customer; +use Magento\Customer\Test\Page\CustomerAccountEdit; + +/** + * Test Flow: + * + * Preconditions: + * 1. Customer is created + * + * Steps: + * 1. Login to fronted as customer from preconditions + * 2. Navigate to Account Information tab + * 3. Check "Change Email" checkbox + * 4. Fill form according to data set and save + * 5. Perform all assertions + * + * @group Security + * @ZephyrId MAGETWO-49041 + */ +class SecureChangingCustomerEmailTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S1'; + /* end tags */ + + /** + * CustomerAccountEdit page. + * + * @var CustomerAccountEdit + */ + protected $customerAccountEdit; + + /** + * Preparing page for test. + * + * @param CustomerAccountEdit $customerAccountEdit + * @return void + */ + public function __inject( + CustomerAccountEdit $customerAccountEdit + ) { + $this->customerAccountEdit = $customerAccountEdit; + } + + /** + * Change customer password in Account Information tab. + * + * @param Customer $initialCustomer + * @param Customer $customer + * @return void + */ + public function test(Customer $initialCustomer, Customer $customer) + { + // Preconditions + $initialCustomer->persist(); + + // Steps + $this->objectManager->create( + \Magento\Customer\Test\TestStep\LoginCustomerOnFrontendStep::class, + ['customer' => $initialCustomer] + )->run(); + + $this->customerAccountEdit->getAccountMenuBlock()->openMenuItem('Account Information'); + $this->customerAccountEdit->getAccountInfoForm()->SetChangeEmail(true); + $this->customerAccountEdit->getAccountInfoForm()->fill($customer); + $this->customerAccountEdit->getAccountInfoForm()->submit(); + } + + /** + * Logout customer from frontend account. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create(\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep::class)->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerEmailTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerEmailTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..dc3ad06e842fd17d9eebb7fd4fdbb2a0815e9d4f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerEmailTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\SecureChangingCustomerEmailTest" summary="Secure Changing Customer Email" ticketId="MAGETWO-49041"> + <variation name="SecureChangingEmailV1" summary="Change Email checkbox is checked, enter new email, correct password"> + <data name="tag" xsi:type="string">severity:S1</data> + <data name="initialCustomer/dataset" xsi:type="string">default</data> + <data name="customer/data/email" xsi:type="string">JaneDoe_%isolation%@example.com</data> + <data name="customer/data/current_password" xsi:type="string">123123^q</data> + <constraint name="Magento\Customer\Test\Constraint\AssertCustomerInfoSuccessSavedMessage" /> + <constraint name="Magento\Security\Test\Constraint\AssertCustomerEmailChanged" /> + </variation> + <variation name="SecureChangingEmailV2" summary="Change Email checkbox is checked, enter new email, wrong password"> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="initialCustomer/dataset" xsi:type="string">default</data> + <data name="customer/data/email" xsi:type="string">JaneDoe_%isolation%@example.com</data> + <data name="customer/data/current_password" xsi:type="string">123123123</data> + <constraint name="Magento\Customer\Test\Constraint\AssertChangePasswordFailMessage" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerPasswordTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerPasswordTest.php new file mode 100644 index 0000000000000000000000000000000000000000..225ef6b397aa4b3c9cf70d2d0ef32adccb73edf6 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerPasswordTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\TestCase; + +use Magento\Mtf\TestCase\Injectable; +use Magento\Customer\Test\Fixture\Customer; +use Magento\Customer\Test\Page\CustomerAccountEdit; + +/** + * Test Flow: + * + * Preconditions: + * 1. Customer is created + * + * Steps: + * 1. Login to fronted as customer from preconditions + * 2. Navigate to Account Information tab + * 3. Check "Change Password" checkbox + * 4. Fill form according to data set and save + * 5. Perform all assertions + * + * @group Security + * @ZephyrId MAGETWO-49043 + */ +class SecureChangingCustomerPasswordTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S1'; + /* end tags */ + + /** + * CustomerAccountEdit page. + * + * @var CustomerAccountEdit + */ + protected $customerAccountEdit; + + /** + * Preparing page for test. + * + * @param CustomerAccountEdit $customerAccountEdit + * @return void + */ + public function __inject( + CustomerAccountEdit $customerAccountEdit + ) { + $this->customerAccountEdit = $customerAccountEdit; + } + + /** + * Change customer password in Account Information tab. + * + * @param Customer $initialCustomer + * @param Customer $customer + * @param boolean $check + * @return void + */ + public function test(Customer $initialCustomer, Customer $customer, $check) + { + // Preconditions + $initialCustomer->persist(); + + // Steps + $this->objectManager->create( + \Magento\Customer\Test\TestStep\LoginCustomerOnFrontendStep::class, + ['customer' => $initialCustomer] + )->run(); + + $this->customerAccountEdit->getAccountMenuBlock()->openMenuItem('Account Information'); + if ($check) { + $this->customerAccountEdit->getAccountInfoForm()->SetChangePassword(true); + $this->customerAccountEdit->getAccountInfoForm()->fill($customer); + $this->customerAccountEdit->getAccountInfoForm()->submit(); + } + } + + /** + * Logout customer from frontend account. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create(\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep::class)->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerPasswordTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerPasswordTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..9fc2f50435a98a13ea42f800efd51b503e11d218 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/SecureChangingCustomerPasswordTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\SecureChangingCustomerPasswordTest" summary="Secure Changing Customer Password" ticketId="MAGETWO-49043"> + <variation name="SecureChangingPasswordV1" summary="Change Password checkbox is checked, enter correct password"> + <data name="tag" xsi:type="string">severity:S1</data> + <data name="initialCustomer/dataset" xsi:type="string">default</data> + <data name="customer/data/current_password" xsi:type="string">123123^q</data> + <data name="customer/data/password" xsi:type="string">123123Qa</data> + <data name="customer/data/password_confirmation" xsi:type="string">123123Qa</data> + <data name="check" xsi:type="string">1</data> + <constraint name="Magento\Customer\Test\Constraint\AssertCustomerInfoSuccessSavedMessage" /> + <constraint name="Magento\Customer\Test\Constraint\AssertCustomerPasswordChanged" /> + </variation> + <variation name="SecureChangingPasswordV2" summary="Change Password checkbox is checked, enter wrong password"> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="initialCustomer/dataset" xsi:type="string">default</data> + <data name="customer/data/current_password" xsi:type="string">123123123</data> + <data name="customer/data/password" xsi:type="string">123123Qa</data> + <data name="customer/data/password_confirmation" xsi:type="string">123123Qa</data> + <data name="check" xsi:type="string">1</data> + <constraint name="Magento\Customer\Test\Constraint\AssertChangePasswordFailMessage" /> + </variation> + <variation name="SecureChangingPasswordV3" summary="Change Password checkbox is unchecked"> + <data name="tag" xsi:type="string">severity:S3</data> + <data name="initialCustomer/dataset" xsi:type="string">default</data> + <data name="check" xsi:type="string">0</data> + <constraint name="Magento\Security\Test\Constraint\AssertDefaultAccountInformation" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php index 43c101176bdac80f4540c013f13b58d9b4a600d9..1314c1503b2f310db1ff80b461b05de986962935 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php @@ -74,7 +74,7 @@ class UpgradeSystemTest extends Injectable ); $version = $upgrade['upgradeVersion']; - $suffix = "( (CE|EE))$"; + $suffix = "( (CE|EE|B2B))$"; $normalVersion = '(0|[1-9]\d*)'; $preReleaseVersion = "((0(?!\\d+(\\.|\\+|{$suffix}))|[1-9A-Za-z])[0-9A-Za-z-]*)"; $buildVersion = '([0-9A-Za-z][0-9A-Za-z-]*)'; diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php b/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php index 93bf1a9001cbfcc4825afee549e3873d1d623ae9..a5fa6d4a83cbe63cbcf6d6b15e5935787d5d2694 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/Fixture/Store/GroupId.php @@ -43,6 +43,15 @@ class GroupId extends DataSource $this->storeGroup = $data['fixture']; $this->data = $this->storeGroup->getWebsiteId() . "/" . $this->storeGroup->getName(); } + + if (isset($data['storeGroup']) && $data['storeGroup'] instanceof StoreGroup) { + $this->storeGroup = $data['storeGroup']; + $this->data = $data['storeGroup']->getWebsiteId() . "/" . $data['storeGroup']->getName(); + } + + if (isset($data['value'])) { + $this->data = $data['value']; + } } /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/Repository/Store.xml b/dev/tests/functional/tests/app/Magento/Store/Test/Repository/Store.xml index 12ca9f5e03a20568423ee565030c785e9eec5bba..37faba3d75524304919c65cf391ee4211ef9fdae 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/Repository/Store.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/Repository/Store.xml @@ -52,5 +52,14 @@ <field name="code" xsi:type="string">store_%isolation%</field> <field name="is_active" xsi:type="string">Enabled</field> </dataset> + + <dataset name="custom_group_custom_store"> + <field name="group_id" xsi:type="array"> + <item name="dataset" xsi:type="string">custom</item> + </field> + <field name="name" xsi:type="string">Custom_Store_%isolation%</field> + <field name="code" xsi:type="string">code_%isolation%</field> + <field name="is_active" xsi:type="string">Enabled</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.php index 804111c497cca364fc749a477ee85e356c686d53..56ec632ffd09f6c063d21eb747d114219fe9b022 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.php @@ -34,6 +34,7 @@ class CreateStoreEntityTest extends Injectable /* tags */ const MVP = 'yes'; const TEST_TYPE = 'acceptance_test, extended_acceptance_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.xml index d0fb3738910cd171f190ee4a5f1c8acc157d298f..26d9aa9beba0acc098558d8fd1f6aacb9083cb5d 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreEntityTest.xml @@ -8,28 +8,27 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\CreateStoreEntityTest" summary="Create Store View" ticketId="MAGETWO-27647"> <variation name="CreateStoreEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S1</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> <data name="store/data/is_active" xsi:type="string">Enabled</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreInGrid" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreForm" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreBackend" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreFrontend" /> </variation> <variation name="CreateStoreEntityTestVariation2"> + <data name="tag" xsi:type="string">severity:S3</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> <data name="store/data/is_active" xsi:type="string">Disabled</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreInGrid" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreForm" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreBackend" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreNotOnFrontend" /> </variation> <variation name="CreateStoreEntityTestVariation3"> + <data name="tag" xsi:type="string">severity:S1</data> <data name="store/data/group_id/dataset" xsi:type="string">custom</data> <data name="store/data/name" xsi:type="string">store_name_%isolation%</data> <data name="store/data/code" xsi:type="string">storecode_%isolation%</data> @@ -41,7 +40,7 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreFrontend" /> </variation> <variation name="CreateStoreEntityTestVariation4" summary="Create New Localized Store View" ticketId="MAGETWO-12405"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">DE_%isolation%</data> <data name="store/data/code" xsi:type="string">de_%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.php index 5d638d781d173ab22923ad2772fd31a984dd944e..76589026b70a031ecea639bb4303b5d38bff9bc0 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.php @@ -29,6 +29,7 @@ class CreateStoreGroupEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml index d3644ff6ddc59c2efab74f853c48c26ca1780aa8..39adbb9c418403469cfd5f82697619c5d89218be 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateStoreGroupEntityTest.xml @@ -8,15 +8,16 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\CreateStoreGroupEntityTest" summary="Create Store Group" ticketId="MAGETWO-27345"> <variation name="CreateStoreGroupEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S1</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">main_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_%isolation%</data> <data name="storeGroup/data/root_category_id/dataset" xsi:type="string">default_category</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessSaveMessage" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupInGrid" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupForm" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupOnStoreViewForm" /> </variation> <variation name="CreateStoreGroupEntityTestVariation2"> + <data name="tag" xsi:type="string">severity:S1</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">custom_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_%isolation%</data> <data name="storeGroup/data/root_category_id/dataset" xsi:type="string">root_category</data> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.php index 766e030b3f61880551fd4b82ffc217ce0aa5179a..3e0b3670f49453c884aa22ce82dbf19eed56cf61 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.php @@ -29,6 +29,7 @@ class CreateWebsiteEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml index e5ec1371587a6ee52955b5b34a6bdc93be5dfbc6..810a4b9ac88f2840c5b160d36a64540f568a626d 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.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\Store\Test\TestCase\CreateWebsiteEntityTest" summary="Create Website" ticketId="MAGETWO-27665"> <variation name="CreateWebsiteEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S1</data> <data name="website/data/name" xsi:type="string">website_%isolation%</data> <data name="website/data/code" xsi:type="string">code_%isolation%</data> <constraint name="Magento\Store\Test\Constraint\AssertWebsiteSuccessSaveMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.php index cbe261fe750a8c7c8fd61244edd5387aea6152cb..baee46f8697034061b748f4d45b5fe62e72b1f5e 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.php @@ -36,6 +36,7 @@ class DeleteStoreEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml index 05132aeefda4cd55afea8f9515023cfd9819b3a0..b888ba1f778370d11e19ded0f8c40cfd543541ea 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.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\Store\Test\TestCase\DeleteStoreEntityTest" summary="Delete Store View" ticketId="MAGETWO-27942"> <variation name="DeleteStoreEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S2</data> <data name="store/dataset" xsi:type="string">custom</data> <data name="createBackup" xsi:type="string">Yes</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessDeleteAndBackupMessages" /> @@ -16,10 +17,10 @@ <constraint name="Magento\Store\Test\Constraint\AssertStoreNotOnFrontend" /> </variation> <variation name="DeleteStoreEntityTestVariation2"> + <data name="tag" xsi:type="string">severity:S2</data> <data name="store/dataset" xsi:type="string">custom</data> <data name="createBackup" xsi:type="string">No</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessDeleteMessage" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreNotInGrid" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreNotOnFrontend" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.php index e7ba27b44c1a845bd513a55bf22d042b3100d1cc..5890298650c5c8ddac676d7198f40ba20dc0a26e 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.php @@ -38,6 +38,7 @@ class DeleteStoreGroupEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S3'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml index bbedbfc8d103786cdef08c5a4dbdc1f94d9af8e9..16c0aeb358bb0fe5e7f0e18216300161c6dfbbf1 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.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\Store\Test\TestCase\DeleteStoreGroupEntityTest" summary="Delete Store Group" ticketId="MAGETWO-27596"> <variation name="DeleteStoreGroupEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S3</data> <data name="storeGroup/dataset" xsi:type="string">custom</data> <data name="createBackup" xsi:type="string">Yes</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessDeleteAndBackupMessages" /> @@ -15,6 +16,7 @@ <constraint name="Magento\Backup\Test\Constraint\AssertBackupInGrid" /> </variation> <variation name="DeleteStoreGroupEntityTestVariation2"> + <data name="tag" xsi:type="string">severity:S3</data> <data name="storeGroup/dataset" xsi:type="string">custom</data> <data name="createBackup" xsi:type="string">No</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.php index 5d4af70cb03e3154364e03611be4347104d09db0..94e14b4a42317edaa2b1f151a3858c770a74fc68 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.php @@ -38,6 +38,7 @@ class DeleteWebsiteEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S3'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.xml index 93c63d42d4de51df71e8fee8eeb62e2286c7bcfc..21327a0ced20bd407f331d8a15c07e42199dfa0d 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteWebsiteEntityTest.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\Store\Test\TestCase\DeleteWebsiteEntityTest" summary="Delete Website" ticketId="MAGETWO-27723"> <variation name="DeleteWebsiteEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S3</data> <data name="website/dataset" xsi:type="string">custom_website</data> <data name="createBackup" xsi:type="string">Yes</data> <constraint name="Magento\Store\Test\Constraint\AssertWebsiteSuccessDeleteAndBackupMessages" /> @@ -15,6 +16,7 @@ <constraint name="Magento\Backup\Test\Constraint\AssertBackupInGrid" /> </variation> <variation name="DeleteWebsiteEntityTestVariation2"> + <data name="tag" xsi:type="string">severity:S3</data> <data name="website/dataset" xsi:type="string">custom_website</data> <data name="createBackup" xsi:type="string">No</data> <constraint name="Magento\Store\Test\Constraint\AssertWebsiteSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9a335e1cbd0e362cda015be6ae1e309fe1f179f6 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Store\Test\TestCase; + +use Magento\Backend\Test\Page\Adminhtml\EditStore; +use Magento\Backend\Test\Page\Adminhtml\StoreIndex; +use Magento\Store\Test\Fixture\Store; +use Magento\Mtf\TestCase\Injectable; +use Magento\Mtf\Fixture\FixtureFactory; + +/** + * Test Move Store view to another store within same website (Store Management) + * + * Test Flow: + * + * Preconditions: + * 1.Create store STA with store view SVA + * 2.Create store STB with store view SVB + * 3.STA and STB belong to the same website + * + * Steps: + * 1. Open Backend + * 2. Go to Stores -> All Stores + * 3. Open store view SVB from grid + * 4. Change store group setting from STB to STA + * 5. Save store entity + * 6. Perform all assertions + * + * @group Store_Management + * @ZephyrId MAGETWO-54026 + */ +class MoveStoreToOtherGroupSameWebsiteTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S1'; + /* end tags */ + + /** + * Page StoreIndex + * + * @var StoreIndex + */ + protected $storeIndex; + + /** + * Page EditStore + * + * @var EditStore + */ + protected $editStore; + + /** + * Preparing pages for test + * + * @param StoreIndex $storeIndex + * @param EditStore $editStore + * @return void + */ + public function __inject(StoreIndex $storeIndex, EditStore $editStore) + { + $this->storeIndex = $storeIndex; + $this->editStore = $editStore; + } + + /** + * Move store view to another store group within a website + * + * @param FixtureFactory $fixtureFactory + * @param Store $storeInitialA + * @param Store $storeInitialB + * @return array + */ + public function test(FixtureFactory $fixtureFactory, Store $storeInitialA, Store $storeInitialB) + { + // Prepare data for constraints + $store = $fixtureFactory->createByCode( + 'store', + [ + 'data' => [ + 'name' => $storeInitialB->getName(), + 'code' => $storeInitialB->getCode(), + 'is_active' => $storeInitialB->getIsActive(), + 'group_id' => [ + 'storeGroup' => $storeInitialA->getDataFieldConfig('group_id')['source']->getStoreGroup() + ], + ], + ] + ); + + // Preconditions + $storeInitialA->persist(); + $storeInitialB->persist(); + + // Steps + $this->storeIndex->open(); + $this->storeIndex->getStoreGrid()->searchAndOpenStore($storeInitialB); + $this->editStore->getStoreForm()->selectStore($storeInitialA->getGroupId()); + $this->editStore->getFormPageActions()->save(); + + return ['store' => $store]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..d6353d8379532e5b3a77dda4696e214d15663c90 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/MoveStoreToOtherGroupSameWebsiteTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Store\Test\TestCase\MoveStoreToOtherGroupSameWebsiteTest" summary="Move Store View" ticketId="MAGETWO-54026"> + <variation name="MoveStoreTestVariation1"> + <data name="storeInitialA/dataset" xsi:type="string">custom_group_custom_store</data> + <data name="storeInitialB/dataset" xsi:type="string">custom_group_custom_store</data> + <constraint name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage" /> + <constraint name="Magento\Store\Test\Constraint\AssertStoreInGrid" /> + <constraint name="Magento\Store\Test\Constraint\AssertStoreForm" /> + <constraint name="Magento\Store\Test\Constraint\AssertStoreBackend" /> + <constraint name="Magento\Store\Test\Constraint\AssertStoreFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.php index 38250d958bc10273d3918789b6b00559bdf0c567..43a219cba62e6404ae9c690050ff582ad712bd39 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.php @@ -33,6 +33,7 @@ class UpdateStoreEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml index 92028c303a5292e7130ced0ae2541aa79111fc29..c32df2c53396b37a89b3419891d2c2bc9e3873ac 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreEntityTest.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\Store\Test\TestCase\UpdateStoreEntityTest" summary="Update Store View" ticketId="MAGETWO-27786"> <variation name="UpdateStoreEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S2</data> <data name="storeInitial/dataset" xsi:type="string">custom</data> <data name="store/data/group_id/dataset" xsi:type="string">default</data> <data name="store/data/name" xsi:type="string">storename_updated%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.php index 7e9143a5c91df4775bc2222a4281171ffb6681b7..f81d0989e24813cf20e1be734beecc5b5b2c5f32 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.php @@ -34,6 +34,7 @@ class UpdateStoreGroupEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml index ce1a67575c67588ff4b9af854b579f5d21859f4b..07309135f607a4bc49584e9aa11819358336a984 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateStoreGroupEntityTest.xml @@ -8,16 +8,17 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Store\Test\TestCase\UpdateStoreGroupEntityTest" summary="Update Store Group" ticketId="MAGETWO-27568"> <variation name="UpdateStoreGroupEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S2</data> <data name="storeGroupOrigin/dataset" xsi:type="string">custom</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">main_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_updated_%isolation%</data> <data name="storeGroup/data/root_category_id/dataset" xsi:type="string">default_category</data> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessSaveMessage" /> - <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupInGrid" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupForm" /> <constraint name="Magento\Store\Test\Constraint\AssertStoreGroupOnStoreViewForm" /> </variation> <variation name="UpdateStoreGroupEntityTestVariation2"> + <data name="tag" xsi:type="string">severity:S2</data> <data name="storeGroupOrigin/dataset" xsi:type="string">custom</data> <data name="storeGroup/data/website_id/dataset" xsi:type="string">custom_website</data> <data name="storeGroup/data/name" xsi:type="string">store_name_updated_%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.php index b187f05856670a1807b904a8b87bfca78ee51357..28fcfd79131faef12d35ad54457deb152b5858ec 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.php @@ -35,6 +35,7 @@ class UpdateWebsiteEntityTest extends Injectable { /* tags */ const MVP = 'yes'; + const SEVERITY = 'S2'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml index 94f0a891e61ccb6f62563d65a3a0f2c86ed9d1f3..c1b3af6749727a658d8bf3635322bd5da6402567 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.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\Store\Test\TestCase\UpdateWebsiteEntityTest" summary="Update Website" ticketId="MAGETWO-27690"> <variation name="UpdateWebsiteEntityTestVariation1"> + <data name="tag" xsi:type="string">severity:S2</data> <data name="websiteOrigin/dataset" xsi:type="string">custom_website</data> <data name="website/data/name" xsi:type="string">website_upd%isolation%</data> <data name="website/data/code" xsi:type="string">code_upd%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Store/Test/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..737f33ebebe1f9ec23a4140cc43e74538d6c48a3 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Store/Test/etc/di.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. 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\Store\Test\Constraint\AssertStoreBackend"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreForm"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreFrontend"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupForm"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupNotInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupOnStoreViewForm"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessDeleteAndBackupMessages"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessDeleteMessage"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreGroupSuccessSaveMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreNotInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreNotOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreSuccessDeleteAndBackupMessages"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreSuccessDeleteMessage"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertStoreSuccessSaveMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteForm"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteNotInGrid"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteOnStoreForm"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteSuccessDeleteAndBackupMessages"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteSuccessDeleteMessage"> + <arguments> + <argument name="severity" xsi:type="string">S3</argument> + </arguments> + </type> + <type name="Magento\Store\Test\Constraint\AssertWebsiteSuccessSaveMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/AbstractFormContainers.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/AbstractFormContainers.php index b6ed37af715be90fc600aa5728744ccd3a160d68..180afa8fe7ec6b3a862ef9f80e077fcca70b354e 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/AbstractFormContainers.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/AbstractFormContainers.php @@ -158,7 +158,7 @@ abstract class AbstractFormContainers extends Form /** * Fill specified form with containers data. - * + * * Input data in format * [[container => [field => [attribute_name => attribute_value, ..], ..], ..] * where container name can be empty if a field is not assigned to any container. diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php index 3fc6f044f88521794f579aa653bacd6d87e44d14..80df7e6f8c631722fb314525b2d6733e2ac605ad 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php @@ -142,7 +142,7 @@ class DataGrid extends Grid * * @var string */ - protected $currentPage = '[data-ui-id="current-page-input"]'; + protected $currentPage = ".//*[@data-ui-id='current-page-input'][not(ancestor::*[@class='sticky-header'])]"; /** * Clear all applied Filters. @@ -347,7 +347,7 @@ class DataGrid extends Grid $this->sortGridByField('ID'); } foreach ($items as $item) { - $this->_rootElement->find($this->currentPage)->setValue(''); + $this->_rootElement->find($this->currentPage, Locator::SELECTOR_XPATH)->setValue(''); $this->waitLoader(); $selectItem = $this->getRow($item)->find($this->selectItem); do { diff --git a/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml index b27bc81d2866045de5c9e1d18fa4689a09a360d1..45076e90aab202b1cae7fbc5abcefbfc6c11e317 100644 --- a/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Ups/Test/TestCase/OnePageCheckoutTest.xml @@ -20,7 +20,7 @@ <data name="cart/data/shipping_method" xsi:type="string">UPS Ground</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, ups, shipping_origin_US_CA</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> @@ -39,7 +39,7 @@ <data name="cart/data/shipping_method" xsi:type="string">UPS Worldwide Expedited</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, ups, shipping_origin_US_CA</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> diff --git a/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml index 6170b8bbe0616f70fa14cdf50722a96199cf92c3..0bd219bab3bfff2103ea9f7a1ff2934ca8e1ad59 100644 --- a/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml @@ -20,7 +20,7 @@ <data name="cart/data/shipping_method" xsi:type="string">Priority Mail 1-Day</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, usps, shipping_origin_US_CA</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> @@ -28,18 +28,16 @@ </variation> <variation name="OnePageCheckoutUspsTestVariation2" summary="Check Out as Guest using USPS with US shipping origin and UK customer"> <data name="products/0" xsi:type="string">catalogProductSimple::default</data> - <data name="products/1" xsi:type="string">configurableProduct::default</data> - <data name="products/2" xsi:type="string">bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">guest</data> <data name="customer/dataset" xsi:type="string">default</data> <data name="address/dataset" xsi:type="string">UK_address</data> <data name="shippingAddress/dataset" xsi:type="string">UK_address</data> <data name="shipping/shipping_service" xsi:type="string">United States Postal Service</data> - <data name="shipping/shipping_method" xsi:type="string">Priority Mail International</data> - <data name="cart/data/shipping_method" xsi:type="string">Priority Mail International</data> + <data name="shipping/shipping_method" xsi:type="string">Priority Mail Express International Flat Rate Envelope</data> + <data name="cart/data/shipping_method" xsi:type="string">Priority Mail Express International Flat Rate Envelope</data> <data name="payment/method" xsi:type="string">checkmo</data> <data name="configData" xsi:type="string">checkmo, usps, shipping_origin_US_CA</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/CreateVaultOrderBackendTest.php b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/CreateVaultOrderBackendTest.php index 3c306280215ea1a2ee460db6f7838fd7c33caf7c..a5194b3915191e7c5ef893bcb5958d610544ddf7 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/CreateVaultOrderBackendTest.php +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/CreateVaultOrderBackendTest.php @@ -38,6 +38,7 @@ class CreateVaultOrderBackendTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = 'acceptance_test, extended_acceptance_test, 3rd_party_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.php b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.php index 17db35df1b44e6d2be519426ba962f6696fee23d..e6462e4697aa612c1239b92b3c7b7e847bd252ba 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.php +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.php @@ -32,6 +32,7 @@ class DeleteSavedCreditCardTest extends Injectable /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S1'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.xml b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.xml index ae303b695277c53272efedc40231bf305b66fea5..ac69bfb9f47e4550e6ab4592cccbdf56cf682da3 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.xml +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/DeleteSavedCreditCardTest.xml @@ -52,7 +52,7 @@ </data> <data name="creditCardSave" xsi:type="string">Yes</data> <data name="configData" xsi:type="string">braintree, payflowpro, braintree_use_vault, payflowpro_use_vault</data> - <data name="tag" xsi:type="string">test_type:3rd_party_test</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/ReorderUsingVaultTest.php b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/ReorderUsingVaultTest.php index c3b82af6949c4a0ea114324abae90aa7ccab3a24..e426662d30e5447599f0b2f4bc47b67902aeb7c9 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/ReorderUsingVaultTest.php +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/ReorderUsingVaultTest.php @@ -36,6 +36,8 @@ class ReorderUsingVaultTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = 'extended_acceptance_test, 3rd_party_test'; + const SEVERITY = 'S1'; + /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/UseVaultOnCheckoutTest.php b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/UseVaultOnCheckoutTest.php index 902ccc62af7f57c23db399bc22d5d3530b0a7515..fb8b3575ee72e463254038446ce55561123e3103 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/UseVaultOnCheckoutTest.php +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/TestCase/UseVaultOnCheckoutTest.php @@ -39,6 +39,7 @@ class UseVaultOnCheckoutTest extends Scenario /* tags */ const MVP = 'yes'; const TEST_TYPE = '3rd_party_test'; + const SEVERITY = 'S0'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Vault/Test/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..aba54057312ea3491655b5ea6e75c9fef4dfc2ae --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. 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\Vault\Test\Constraint\AssertCreditCardNotPresentOnCheckout"> + <arguments> + <argument name="severity" xsi:type="string">S2</argument> + </arguments> + </type> + <type name="Magento\Vault\Test\Constraint\AssertStoredPaymentDeletedMessage"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Vault/Test/etc/testcase.xml index 6b4f31a329d679872c48841cfad346c53012ac7d..cd34b9664820892b4275c93e7b7e43afaaf2f1e4 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/etc/testcase.xml @@ -37,7 +37,8 @@ <step name="selectStore" module="Magento_Sales" next="addProducts" /> <step name="addProducts" module="Magento_Sales" next="fillAccountInformation" /> <step name="fillAccountInformation" module="Magento_Sales" next="fillBillingAddress" /> - <step name="fillBillingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> + <step name="fillBillingAddress" module="Magento_Sales" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Sales" next="selectShippingMethodForOrder" /> <step name="selectShippingMethodForOrder" module="Magento_Sales" next="saveCreditCardOnBackend" /> <step name="saveCreditCardOnBackend" module="Magento_Vault" next="submitOrder" /> <step name="submitOrder" module="Magento_Sales" next="reorder" /> diff --git a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/basic.xml b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/basic.xml index 4b4ab839d3463cbef4c766161e41874d0d65e446..6836259eb671ac908ad017138bde36a86e764766 100644 --- a/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/basic.xml +++ b/dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests/basic.xml @@ -7,11 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../vendor/magento/mtf/Magento/Mtf/TestRunner/etc/testRunner.xsd"> - <rule scope="testcase"> - <deny> - <tag group="test_type" value="3rd_party_test_deprecated" /> - </deny> - </rule> <rule scope="variation"> <deny> <tag group="test_type" value="3rd_party_test" /> diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Model/PaymentMethodListTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Model/PaymentMethodListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0e38adda5498facd9f6213193f536128f2b20c8f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Model/PaymentMethodListTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Model; + +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider as PayPalConfigProvider; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Vault\Api\PaymentMethodListInterface; +use Magento\Vault\Model\VaultPaymentInterface; + +/** + * Contains tests for vault payment list methods + */ +class PaymentMethodListTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var PaymentMethodListInterface + */ + private $paymentMethodList; + + /** + * @var int + */ + private $storeId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->storeId = $objectManager->get(StoreManagerInterface::class) + ->getStore() + ->getId(); + $this->paymentMethodList = $objectManager->get(PaymentMethodListInterface::class); + } + + /** + * @magentoDataFixture Magento/Braintree/_files/payments.php + */ + public function testGetList() + { + $vaultPayments = $this->paymentMethodList->getList($this->storeId); + + static::assertNotEmpty($vaultPayments); + + $paymentCodes = array_map(function (VaultPaymentInterface $payment) { + return $payment->getCode(); + }, $vaultPayments); + + $expectedCodes = [ + PayPalConfigProvider::PAYPAL_VAULT_CODE, + ConfigProvider::CC_VAULT_CODE + ]; + static::assertNotEmpty(array_intersect($expectedCodes, $paymentCodes)); + } + + /** + * @magentoDataFixture Magento/Braintree/_files/payments.php + */ + public function testGetActiveList() + { + $vaultPayments = $this->paymentMethodList->getActiveList($this->storeId); + + static::assertNotEmpty($vaultPayments); + static::assertCount(1, $vaultPayments); + $payment = array_pop($vaultPayments); + static::assertEquals(PayPalConfigProvider::PAYPAL_VAULT_CODE, $payment->getCode()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Braintree/_files/payments.php b/dev/tests/integration/testsuite/Magento/Braintree/_files/payments.php new file mode 100644 index 0000000000000000000000000000000000000000..99ff4481f2982f5076282161931017893380e491 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/_files/payments.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider; +use Magento\Config\Model\Config; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Config $config */ +$config = $objectManager->get(Config::class); +$config->setDataByPath('payment/' . ConfigProvider::PAYPAL_CODE . '/active', 1); +$config->save(); +$config->setDataByPath('payment/' . ConfigProvider::PAYPAL_VAULT_CODE . '/active', 1); +$config->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4738e1886be3e6d9ab64f65cd7f241b63453a741 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Controller\Adminhtml\Product\Set; + +use Magento\Eav\Api\AttributeSetRepositoryInterface; +use Magento\Eav\Api\Data\AttributeSetInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; + +class SaveTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** + * @magentoDataFixture Magento/Catalog/_files/attribute_set_with_renamed_group.php + */ + public function testAlreadyExistsExceptionProcessingWhenGroupCodeIsDuplicated() + { + $attributeSet = $this->getAttributeSetByName('attribute_set_test'); + $this->assertNotEmpty($attributeSet, 'Attribute set with name "attribute_set_test" is missed'); + + $this->getRequest()->setPostValue('data', json_encode([ + 'attribute_set_name' => 'attribute_set_test', + 'groups' => [ + ['ynode-418', 'attribute-group-name', 1], + ], + 'attributes' => [ + ['9999', 'ynode-418', 1, null] + ], + 'not_attributes' => [], + 'removeGroups' => [], + ])); + $this->dispatch('backend/catalog/product_set/save/id/' . $attributeSet->getAttributeSetId()); + + $jsonResponse = json_decode($this->getResponse()->getBody()); + $this->assertNotNull($jsonResponse); + $this->assertEquals(1, $jsonResponse->error); + $this->assertContains( + 'Attribute group with same code already exist. Please rename "attribute-group-name" group', + $jsonResponse->message + ); + } + + /** + * @param string $attributeSetName + * @return AttributeSetInterface|null + */ + protected function getAttributeSetByName($attributeSetName) + { + $objectManager = Bootstrap::getObjectManager(); + + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); + $searchCriteriaBuilder->addFilter('attribute_set_name', $attributeSetName); + + /** @var AttributeSetRepositoryInterface $attributeSetRepository */ + $attributeSetRepository = $objectManager->get(AttributeSetRepositoryInterface::class); + $result = $attributeSetRepository->getList($searchCriteriaBuilder->create()); + + $items = $result->getItems(); + return $result->getTotalCount() ? array_pop($items) : null; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/CreateHandlerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/CreateHandlerTest.php index 992c3bf310f51995ece1b78f6c566fd1d3baf1a4..2c42a289bd4c017a15591d9a110220fce74de346 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/CreateHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/CreateHandlerTest.php @@ -18,6 +18,10 @@ class CreateHandlerTest extends \PHPUnit_Framework_TestCase */ protected $createHandler; + private $fileName = '/m/a/magento_image.jpg'; + + private $fileLabel = 'Magento image'; + protected function setUp() { $this->createHandler = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( @@ -28,10 +32,8 @@ class CreateHandlerTest extends \PHPUnit_Framework_TestCase /** * @covers \Magento\Catalog\Model\Product\Gallery\CreateHandler::execute */ - public function testExecute() + public function testExecuteWithImageDuplicate() { - $fileName = '/m/a/magento_image.jpg'; - $fileLabel = 'Magento image'; /** @var $product \Magento\Catalog\Model\Product */ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\Product::class @@ -39,20 +41,141 @@ class CreateHandlerTest extends \PHPUnit_Framework_TestCase $product->load(1); $product->setData( 'media_gallery', - ['images' => ['image' => ['file' => $fileName, 'label' => $fileLabel]]] + ['images' => ['image' => ['file' => $this->fileName, 'label' => $this->fileLabel]]] ); - $product->setData('image', $fileName); + $product->setData('image', $this->fileName); $this->createHandler->execute($product); $this->assertStringStartsWith('/m/a/magento_image', $product->getData('media_gallery/images/image/new_file')); - $this->assertEquals($fileLabel, $product->getData('image_label')); + $this->assertEquals($this->fileLabel, $product->getData('image_label')); $product->setIsDuplicate(true); $product->setData( 'media_gallery', - ['images' => ['image' => ['value_id' => '100', 'file' => $fileName, 'label' => $fileLabel]]] + ['images' => ['image' => ['value_id' => '100', 'file' => $this->fileName, 'label' => $this->fileLabel]]] ); $this->createHandler->execute($product); $this->assertStringStartsWith('/m/a/magento_image', $product->getData('media_gallery/duplicate/100')); - $this->assertEquals($fileLabel, $product->getData('image_label')); + $this->assertEquals($this->fileLabel, $product->getData('image_label')); + } + + /** + * @dataProvider executeDataProvider + * @param $image + * @param $smallImage + * @param $swatchImage + * @param $thumbnail + */ + public function testExecuteWithImageRoles($image, $smallImage, $swatchImage, $thumbnail) + { + /** @var $product \Magento\Catalog\Model\Product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\Product::class + ); + $product->load(1); + $product->setData( + 'media_gallery', + ['images' => ['image' => ['file' => $this->fileName, 'label' => '']]] + ); + $product->setData('image', $image); + $product->setData('small_image', $smallImage); + $product->setData('swatch_image', $swatchImage); + $product->setData('thumbnail', $thumbnail); + $this->createHandler->execute($product); + + $resource = $product->getResource(); + $id = $product->getId(); + $storeId = $product->getStoreId(); + + $this->assertStringStartsWith('/m/a/magento_image', $product->getData('media_gallery/images/image/new_file')); + $this->assertEquals( + $image, + $resource->getAttributeRawValue($id, $resource->getAttribute('image'), $storeId) + ); + $this->assertEquals( + $smallImage, + $resource->getAttributeRawValue($id, $resource->getAttribute('small_image'), $storeId) + ); + $this->assertEquals( + $swatchImage, + $resource->getAttributeRawValue($id, $resource->getAttribute('swatch_image'), $storeId) + ); + $this->assertEquals( + $thumbnail, + $resource->getAttributeRawValue($id, $resource->getAttribute('thumbnail'), $storeId) + ); + } + + /** + * @dataProvider executeDataProvider + * @param $image + * @param $smallImage + * @param $swatchImage + * @param $thumbnail + */ + public function testExecuteWithoutImages($image, $smallImage, $swatchImage, $thumbnail) + { + /** @var $product \Magento\Catalog\Model\Product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\Product::class + ); + $product->load(1); + $product->setData( + 'media_gallery', + ['images' => ['image' => ['file' => $this->fileName, 'label' => '']]] + ); + $product->setData('image', $image); + $product->setData('small_image', $smallImage); + $product->setData('swatch_image', $swatchImage); + $product->setData('thumbnail', $thumbnail); + $this->createHandler->execute($product); + + $product->unsetData('image'); + $product->unsetData('small_image'); + $product->unsetData('swatch_image'); + $product->unsetData('thumbnail'); + $this->createHandler->execute($product); + + $resource = $product->getResource(); + $id = $product->getId(); + $storeId = $product->getStoreId(); + + $this->assertStringStartsWith('/m/a/magento_image', $product->getData('media_gallery/images/image/new_file')); + $this->assertEquals( + $image, + $resource->getAttributeRawValue($id, $resource->getAttribute('image'), $storeId) + ); + $this->assertEquals( + $smallImage, + $resource->getAttributeRawValue($id, $resource->getAttribute('small_image'), $storeId) + ); + $this->assertEquals( + $swatchImage, + $resource->getAttributeRawValue($id, $resource->getAttribute('swatch_image'), $storeId) + ); + $this->assertEquals( + $thumbnail, + $resource->getAttributeRawValue($id, $resource->getAttribute('thumbnail'), $storeId) + ); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + [ + 'image' => $this->fileName, + 'small_image' => $this->fileName, + 'swatch_image' => $this->fileName, + 'thumbnail' => $this->fileName + ], + [ + 'image' => 'no_selection', + 'small_image' => 'no_selection', + 'swatch_image' => 'no_selection', + 'thumbnail' => 'no_selection' + ] + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index f610ee5e15473c89cc2f4fe4f6f3cc9e92a48eac..3c27a65c4a74385f546c108c077c128ebb4edcfb 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -476,13 +476,24 @@ class ProductTest extends \PHPUnit_Framework_TestCase } /** - * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_options.php * @magentoAppIsolation enabled */ public function testGetOptions() { - $this->_model = $this->productRepository->get('simple'); - - $this->assertEquals(4, count($this->_model->getOptions())); + $this->_model = $this->productRepository->get('simple_with_custom_options'); + $options = $this->_model->getOptions(); + $this->assertNotEmpty($options); + $expectedValue = [ + '3-1-select' => 3000.00, + '3-2-select' => 5000.00, + '4-1-radio' => 600.234, + '4-2-radio' => 40000.00 + ]; + foreach ($options as $option) { + foreach ($option->getValues() as $value) { + $this->assertEquals($expectedValue[$value->getSku()], floatval($value->getPrice())); + } + } } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_with_renamed_group.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_with_renamed_group.php new file mode 100644 index 0000000000000000000000000000000000000000..d73baa9e70e023d55e0a3306a03f8b7a56666b86 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_with_renamed_group.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Catalog\Api\AttributeSetRepositoryInterface; +use Magento\Eav\Api\AttributeGroupRepositoryInterface; +use Magento\Eav\Api\Data\AttributeGroupInterface; +use Magento\Eav\Api\Data\AttributeGroupInterfaceFactory; +use Magento\Eav\Api\Data\AttributeSetInterface; +use Magento\Eav\Api\Data\AttributeSetInterfaceFactory; +use Magento\Eav\Model\Entity\Type; +use Magento\Framework\Api\DataObjectHelper; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$attributeSetFactory = $objectManager->get(AttributeSetInterfaceFactory::class); +$attributeGroupFactory = $objectManager->get(AttributeGroupInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = $objectManager->get(DataObjectHelper::class); +/** @var AttributeGroupRepositoryInterface $attributeGroupRepository */ +$attributeGroupRepository = $objectManager->get(AttributeGroupRepositoryInterface::class); +/** @var AttributeSetRepositoryInterface $attributeSetRepository */ +$attributeSetRepository = $objectManager->get(AttributeSetRepositoryInterface::class); + +/** @var AttributeSetInterface $attributeSet */ +$attributeSet = $attributeSetFactory->create(); +$entityTypeId = $objectManager->create(Type::class)->loadByCode('catalog_product')->getId(); +$dataObjectHelper->populateWithArray( + $attributeSet, + [ + 'attribute_set_name' => 'attribute_set_test', + 'entity_type_id' => $entityTypeId, + ], + AttributeSetInterface::class +); +$attributeSetRepository->save($attributeSet); + +/** @var AttributeGroupInterface $attributeGroup */ +$attributeGroup = $attributeGroupFactory->create(); +$dataObjectHelper->populateWithArray( + $attributeGroup, + [ + 'attribute_set_id' => $attributeSet->getAttributeSetId(), + 'attribute_group_name' => 'attribute-group-name', + 'default_id' => 1, + ], + AttributeGroupInterface::class +); +$attributeGroupRepository->save($attributeGroup); + +// during renaming group code is not changed +$attributeGroup->setAttributeGroupName('attribute-group-renamed'); +$attributeGroupRepository->save($attributeGroup); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php index 2c9d6ca91e9f9cd2487067389fc119b24ca4a3bc..963de3e46fea69c821902b8d4266681adc0d5ea8 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php @@ -40,7 +40,7 @@ $attribute->setData( 'option_1' => ['Option 1'], 'option_2' => ['Option 2'], 'option_3' => ['Option 3'], - 'option_4' => ['Option 4 "!@#$%^&*'], + 'option_4' => ['Option 4 "!@#$%^&*'] ], 'order' => [ 'option_1' => 1, diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php new file mode 100644 index 0000000000000000000000000000000000000000..1ab2f9ef65bffca50886f8c7ef4764c550da0aa5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/* Create attribute */ +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Setup\CategorySetup::class +); +/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class +); +$attribute->setData( + [ + 'attribute_code' => 'multiselect_attribute', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'multiselect', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 0, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Multiselect Attribute'], + 'backend_type' => 'varchar', + 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, + 'option' => [ + 'value' => [ + 'option_1' => ['Opt|,=ion 1'], + 'option_2' => ['Opt||,ion 2'], + 'option_3' => ['Option 3 "!@#$%^&*, "|"'] + ], + 'order' => [ + 'option_1' => 1, + 'option_2' => 2, + 'option_3' => 3, + ], + ], + ] +); +$attribute->save(); + +/* Assign attribute to attribute set */ +$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php index aadf1e74a883ce823d574178cba29ca617f5abf0..3125cc2a690ba9a0788079f37be339e5e6b5c28f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php @@ -160,14 +160,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '3-1-select', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', @@ -183,14 +183,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '4-1-radio', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php index 18c70968039f8f6df0056daa98da1dbef068e055..d933cd00656d3c8ffde5bd37526aa31aaacba681 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_admin_store.php @@ -98,14 +98,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '3-1-select', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', @@ -121,14 +121,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '4-1-radio', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php new file mode 100644 index 0000000000000000000000000000000000000000..a0a29753bfeed384aa1a4131e4ca2fed26491e95 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); + +/** @var \Magento\TestFramework\ObjectManager $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple_with_custom_options') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ] + )->setCanSaveCustomOptions(true) + ->setHasOptions(true); + +$oldOptions = [ + [ + 'previous_group' => 'select', + 'title' => 'Test Select', + 'type' => 'drop_down', + 'is_require' => 1, + 'sort_order' => 0, + 'values' => [ + [ + 'option_type_id' => null, + 'title' => 'Option 1', + 'price' => '3,000.00', + 'price_type' => 'fixed', + 'sku' => '3-1-select', + ], + [ + 'option_type_id' => null, + 'title' => 'Option 2', + 'price' => '5,000.00', + 'price_type' => 'fixed', + 'sku' => '3-2-select', + ], + ] + ], + [ + 'previous_group' => 'select', + 'title' => 'Test Radio', + 'type' => 'radio', + 'is_require' => 1, + 'sort_order' => 0, + 'values' => [ + [ + 'option_type_id' => null, + 'title' => 'Option 1', + 'price' => '600.234', + 'price_type' => 'fixed', + 'sku' => '4-1-radio', + ], + [ + 'option_type_id' => null, + 'title' => 'Option 2', + 'price' => '40,000.00', + 'price_type' => 'fixed', + 'sku' => '4-2-radio', + ], + ] + ] +]; + +$options = []; + +/** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = $objectManager->create(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); + +foreach ($oldOptions as $option) { + /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option */ + $option = $customOptionFactory->create(['data' => $option]); + $option->setProductSku($product->getSku()); + + $options[] = $option; +} + +$product->setOptions($options); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ +$productRepositoryFactory = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepositoryFactory->save($product); + +$categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [2] +); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..88e0fb198576060eb8295e633a985d9d6e3ff96a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options_rollback.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +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_with_custom_options', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { + +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index 9b06764ce264cfe9aaa6be03a6e61f2f01210631..dc9e440b045f86ad2e85de45124ac01881d3892c 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -206,4 +206,55 @@ class ProductTest extends \PHPUnit_Framework_TestCase $data = $model->setWriter($exportAdapter)->export(); $this->assertEmpty($data); } + + /** + * Verify if fields wrapping works correct when "Fields Enclosure" option enabled + * + * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data.php + */ + public function testExportWithFieldsEnclosure() + { + $this->model->setParameters([ + \Magento\ImportExport\Model\Export::FIELDS_ENCLOSURE => 1 + ]); + + $this->model->setWriter( + $this->objectManager->create( + \Magento\ImportExport\Model\Export\Adapter\Csv::class + ) + ); + $exportData = $this->model->export(); + + $this->assertContains('""Option 2""', $exportData); + $this->assertContains('""Option 3""', $exportData); + $this->assertContains('""Option 4 """"!@#$%^&*""', $exportData); + $this->assertContains('text_attribute=""!@#$%^&*()_+1234567890-=|\:;""""\'<,>.?/', $exportData); + } + + /** + * Verify that "category ids" filter correctly applies to export result + * + * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_with_categories.php + */ + public function testCategoryIdsFilter() + { + $this->model->setWriter( + $this->objectManager->create( + \Magento\ImportExport\Model\Export\Adapter\Csv::class + ) + ); + + $this->model->setParameters([ + \Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP => [ + 'category_ids' => '2,13' + ] + ]); + + $exportData = $this->model->export(); + + $this->assertContains('Simple Product', $exportData); + $this->assertContains('Simple Product Three', $exportData); + $this->assertNotContains('Simple Product Two', $exportData); + $this->assertNotContains('Simple Product Not Visible On Storefront', $exportData); + } } 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 9f3d16a998987d600e1f702f4a4228e1ff06c9b7..9ab7db147d811d370a4c2609d5baf5ea35225680 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -20,6 +20,7 @@ use Magento\Catalog\Model\Category; use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Model\Import; +use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; /** * Class ProductTest @@ -387,9 +388,13 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase ); $productAfterImport->load($productBeforeImport->getId()); $this->assertEquals( - @strtotime($row['news_from_date']), + @strtotime(date('m/d/Y', @strtotime($row['news_from_date']))), @strtotime($productAfterImport->getNewsFromDate()) ); + $this->assertEquals( + @strtotime($row['news_to_date']), + @strtotime($productAfterImport->getNewsToDate()) + ); unset($productAfterImport); } unset($productsBeforeImport, $product); @@ -1050,9 +1055,9 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase * @magentoAppIsolation enabled * @dataProvider validateUrlKeysDataProvider * @param $importFile string - * @param $errorsCount int + * @param $expectedErrors array */ - public function testValidateUrlKeys($importFile, $errorsCount) + public function testValidateUrlKeys($importFile, $expectedErrors) { $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Framework\Filesystem::class @@ -1066,19 +1071,16 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase 'directory' => $directory ] ); + /** @var \Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface $errors */ $errors = $this->_model->setParameters( ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product'] )->setSource( $source )->validateData(); - - $this->assertTrue($errors->getErrorsCount() == $errorsCount); - if ($errorsCount >= 1) { - $this->assertEquals( - "Specified URL key already exists", - $errors->getErrorByRowNumber(1)[0]->getErrorMessage() - ); - } + $this->assertEquals( + $expectedErrors[RowValidatorInterface::ERROR_DUPLICATE_URL_KEY], + count($errors->getErrorsByCode([RowValidatorInterface::ERROR_DUPLICATE_URL_KEY])) + ); } /** @@ -1087,9 +1089,24 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase public function validateUrlKeysDataProvider() { return [ - ['products_to_check_valid_url_keys.csv', 0], - ['products_to_check_duplicated_url_keys.csv', 2], - ['products_to_check_duplicated_names.csv' , 1] + [ + 'products_to_check_valid_url_keys.csv', + [ + RowValidatorInterface::ERROR_DUPLICATE_URL_KEY => 0 + ] + ], + [ + 'products_to_check_duplicated_url_keys.csv', + [ + RowValidatorInterface::ERROR_DUPLICATE_URL_KEY => 2 + ] + ], + [ + 'products_to_check_duplicated_names.csv' , + [ + RowValidatorInterface::ERROR_DUPLICATE_URL_KEY => 1 + ] + ] ]; } @@ -1363,4 +1380,55 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase array_values($actualProductSkus) ); } + + /** + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php + * @magentoDataFixture Magento/Catalog/_files/product_text_attribute.php + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + */ + public function testProductWithWrappedAdditionalAttributes() + { + $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/_files/products_to_import_with_additional_attributes.csv', + 'directory' => $directory + ] + ); + $errors = $this->_model->setParameters( + [ + 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, + 'entity' => 'catalog_product', + \Magento\ImportExport\Model\Import::FIELDS_ENCLOSURE => 1 + ] + )->setSource( + $source + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + + $this->_model->importData(); + + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Api\ProductRepositoryInterface::class + ); + + /** @var \Magento\Eav\Api\AttributeOptionManagementInterface $multiselectOptions */ + $multiselectOptions = $this->objectManager->get(\Magento\Eav\Api\AttributeOptionManagementInterface::class) + ->getItems(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_attribute'); + + $product1 = $productRepository->get('simple1'); + $this->assertEquals('\'", =|', $product1->getData('text_attribute')); + $this->assertEquals(implode(',', [$multiselectOptions[3]->getValue(), $multiselectOptions[2]->getValue()]), + $product1->getData('multiselect_attribute')); + + $product2 = $productRepository->get('simple2'); + $this->assertEquals('', $product2->getData('text_attribute')); + $this->assertEquals(implode(',', [$multiselectOptions[1]->getValue(), $multiselectOptions[2]->getValue()]), + $product2->getData('multiselect_attribute')); + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_additional_attributes.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_additional_attributes.csv new file mode 100644 index 0000000000000000000000000000000000000000..eb882be4c6bb1dcb804cd284e1ce376c31feec37 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_additional_attributes.csv @@ -0,0 +1,3 @@ +sku,product_type,name,price,attribute_set_code,categories,additional_attributes +simple1,simple,"simple 1",25,Default,"Default Category/Category 1","text_attribute=""'"""", =|"",multiselect_attribute=""Option 3 """"!@#$%^&*, """"|""""""|""Opt||,ion 2""" +simple2,simple,"simple 2",34,Default,"Default Category/Category 1","multiselect_attribute=""Opt|,=ion 1""|""Opt||,ion 2"",text_attribute=""""" diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv index ae7e27dbd95c0984548142c93c5d4e35afa0cc20..265e29fab65bb88f469454a7c77185c654e26acc 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv @@ -1,4 +1,4 @@ -sku,news_from_date -simple1,"1/1/2015 20:00" -simple2,"10/8/2012 23:58" -simple3,"12/31/1998 17:30" +sku,news_from_date,news_to_date +simple1,"1/1/2015 20:00","1/2/2015" +simple2,"10/8/2012 23:58","1/3/2015" +simple3,"12/31/1998 17:30","1/4/2015" diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php index a3bf1686b5fdb2181f28939a1bc10d36170532ea..ffee568b0622fa8ef3f5af185e24805367ee9203 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data.php @@ -18,16 +18,30 @@ $productModel = $objectManager->create(\Magento\Catalog\Model\Product::class); $customOptions = [ [ - 'id' => 'test_option_code_1', - 'option_id' => '0', + 'option_id' => null, 'sort_order' => '0', 'title' => 'Option 1', 'type' => 'drop_down', 'is_require' => 1, 'values' => [ - 1 => ['option_type_id' => -1, 'title' => 'Option 1 & Value 1"', 'price' => '1.00', 'price_type' => 'fixed'], - 2 => ['option_type_id' => -1, 'title' => 'Option 1 & Value 2"', 'price' => '2.00', 'price_type' => 'fixed'], - 3 => ['option_type_id' => -1, 'title' => 'Option 1 & Value 3"', 'price' => '3.00', 'price_type' => 'fixed'] + 1 => [ + 'option_type_id' => null, + 'title' => 'Option 1 & Value 1"', + 'price' => '1.00', + 'price_type' => 'fixed' + ], + 2 => [ + 'option_type_id' => null, + 'title' => 'Option 1 & Value 2"', + 'price' => '2.00', + 'price_type' => 'fixed' + ], + 3 => [ + 'option_type_id' => null, + 'title' => 'Option 1 & Value 3"', + 'price' => '3.00', + 'price_type' => 'fixed' + ] ] ], [ diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_categories.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_categories.php new file mode 100644 index 0000000000000000000000000000000000000000..e65ff00bcffcabd1e2fdeae3810876178e0d06aa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_categories.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); + +require dirname(dirname(__DIR__)) . '/Catalog/_files/categories.php'; diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php index 7fb35a4412bf8bbbda969b646a23eee84aac61ac..ec230f3c5126185e21d27226c65f6e09e7f125e5 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method_and_items_categories.php @@ -56,7 +56,7 @@ $product->setTypeId( )->setId( 444 )->setAttributeSetId( - 5 + 4 )->setStoreId( 1 )->setWebsiteIds( diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php index 65e578c1181db4cc44d463d62bf210d4a26a55ee..525b3161ffcd921b6b0b9ea048b1f8e077c094f8 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Cms\Model; +use Magento\Cms\Api\PageRepositoryInterface; + /** * @magentoAppArea adminhtml */ @@ -44,6 +46,22 @@ class PageTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedIdentifier, $page->getIdentifier()); } + /** + * @magentoDbIsolation enabled + */ + public function testUpdateTime() + { + $updateTime = '2016-09-01 00:00:00'; + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Cms\Model\Page $page */ + $page = $objectManager->create(\Magento\Cms\Model\Page::class); + $page->setData(['title' => 'Test', 'stores' => [1]]); + $page->setUpdateTime($updateTime); + $page->save(); + $page = $objectManager->get(PageRepositoryInterface::class)->getById($page->getId()); + $this->assertEquals($updateTime, $page->getUpdateTime()); + } + public function generateIdentifierFromTitleDataProvider() { return [ diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php index 053e33da09fe9f812aa7a1d4164cf274467a3a6c..6322a60cef8a5fda1b50e220b10de7c5385956d4 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Product/Type/Configurable/PriceTest.php @@ -90,8 +90,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase $options = $this->prepareOptions( [ [ - 'id' => 1, - 'option_id' => 0, + 'option_id' => null, 'previous_group' => 'text', 'title' => 'Test Field', 'type' => 'field', diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php index b35d87d48063a39011a92e5184090402ea64b4c0..ff08056e4b18def3f520d2a5d9f2090b168d5ec8 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php @@ -104,14 +104,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '3-1-select', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', @@ -127,14 +127,14 @@ $oldOptions = [ 'sort_order' => 0, 'values' => [ [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 1', 'price' => 3, 'price_type' => 'fixed', 'sku' => '4-1-radio', ], [ - 'option_type_id' => -1, + 'option_type_id' => null, 'title' => 'Option 2', 'price' => 3, 'price_type' => 'fixed', diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f3571c17ddffe72511037e07af3617244831f66b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Controller\Adminhtml\Index; + +/** + * ResetPassword controller test. + * + * @magentoAppArea adminhtml + */ +class ResetPasswordTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** + * Base controller URL + * + * @var string + */ + protected $baseControllerUrl = 'http://localhost/index.php/backend/customer/index/'; + + /** + * Checks reset password functionality with default settings and customer reset request event. + * + * @magentoConfigFixture current_store admin/security/limit_password_reset_requests_method 1 + * @magentoConfigFixture current_store admin/security/min_time_between_password_reset_requests 10 + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testResetPasswordSuccess() + { + $this->passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST + ); + $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->dispatch('backend/customer/index/resetPassword'); + $this->assertSessionMessages( + $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')); + } + + /** + * Checks reset password functionality with default settings, customer and admin reset request events. + * + * @magentoConfigFixture current_store admin/security/limit_password_reset_requests_method 1 + * @magentoConfigFixture current_store admin/security/min_time_between_password_reset_requests 10 + * @magentoConfigFixture current_store contact/email/recipient_email hello@example.com + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testResetPasswordWithSecurityViolationException() + { + $this->passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST + ); + $this->passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST + ); + $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->dispatch('backend/customer/index/resetPassword'); + $this->assertSessionMessages( + $this->equalTo( + ['Too many password reset requests. Please wait and try again or contact hello@example.com.'] + ), + \Magento\Framework\Message\MessageInterface::TYPE_ERROR + ); + $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl . 'edit')); + } + + /** + * Create and save reset request event with provided request type. + * + * @param int $requestType + */ + private function passwordResetRequestEventCreate($requestType) + { + $passwordResetRequestEventFactory = $this->_objectManager->get( + \Magento\Security\Model\PasswordResetRequestEventFactory::class + ); + $passwordResetRequestEvent = $passwordResetRequestEventFactory->create(); + $passwordResetRequestEvent + ->setRequestType($requestType) + ->setAccountReference('customer@example.com') + ->setCreatedAt(strtotime('now')) + ->setIp('3232249856') + ->save(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Backup/FilesystemTest.php b/dev/tests/integration/testsuite/Magento/Framework/Backup/FilesystemTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a28a92d9658d60dd804d8a900e231bf79f23cb62 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Backup/FilesystemTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Backup; + +use \Magento\TestFramework\Helper\Bootstrap; +use \Magento\Framework\App\Filesystem\DirectoryList; + +class FilesystemTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var \Magento\Framework\Backup\Filesystem + */ + private $filesystem; + + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->filesystem = $this->objectManager->create(\Magento\Framework\Backup\Filesystem::class); + } + + /** + * @magentoAppIsolation enabled + */ + public function testRollback() + { + $rootDir = Bootstrap::getInstance()->getAppTempDir() + . '/rollback_test_' . time(); + $backupsDir = __DIR__ . '/_files/var/backups'; + $fileName = 'test.txt'; + + mkdir($rootDir); + + $this->filesystem->setRootDir($rootDir) + ->setBackupsDir($backupsDir) + ->setTime(1474538269) + ->setName('code') + ->setBackupExtension('tgz'); + + $this->assertTrue($this->filesystem->rollback()); + $this->assertTrue(file_exists($rootDir . '/' . $fileName)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Backup/_files/var/backups/1474538269_filesystem_code.tgz b/dev/tests/integration/testsuite/Magento/Framework/Backup/_files/var/backups/1474538269_filesystem_code.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6e972c35672a8fbee6f29c71c415648b410275f1 Binary files /dev/null and b/dev/tests/integration/testsuite/Magento/Framework/Backup/_files/var/backups/1474538269_filesystem_code.tgz differ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php b/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php index 341b710d5a82f3bf15c831650663345be000c761..324e93770f048d3e76eca7bc9d7219132808f562 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php @@ -547,6 +547,9 @@ class InterfaceTest extends \PHPUnit_Framework_TestCase */ public function testCreatePngFromString($pixel1, $expectedColor1, $pixel2, $expectedColor2, $adapterType) { + if (!function_exists('imagettfbbox')) { + $this->markTestSkipped('Workaround for problem with imagettfbbox() function on Travis'); + } $adapter = $this->_getAdapter($adapterType); /** @var \Magento\Framework\Filesystem\Directory\ReadFactory readFactory */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Session/SaveHandlerTest.php b/dev/tests/integration/testsuite/Magento/Framework/Session/SaveHandlerTest.php index 7ba149d361f4fcd15c9d5989ff619d9f81b53cc2..40fafeceae44cb8342e03de9cb3a204675c52a88 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Session/SaveHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Session/SaveHandlerTest.php @@ -29,32 +29,20 @@ class SaveHandlerTest extends \PHPUnit_Framework_TestCase */ public function testSetSaveHandler($deploymentConfigHandler, $iniHandler) { - $this->markTestSkipped('MAGETWO-56529'); - // Set expected session.save_handler config - if ($deploymentConfigHandler) { - if ($deploymentConfigHandler !== 'files') { - $expected = 'user'; - } else { - $expected = $deploymentConfigHandler; - } - } else if ($iniHandler) { - $expected = $iniHandler; - } else { - $expected = SaveHandlerInterface::DEFAULT_HANDLER; - } + $expected = $this->getExpectedSaveHandler($deploymentConfigHandler, $iniHandler); // Set ini configuration if ($iniHandler) { ini_set('session.save_handler', $iniHandler); } - + $defaultHandler = ini_get('session.save_handler') ?: SaveHandlerInterface::DEFAULT_HANDLER; /** @var DeploymentConfig | \PHPUnit_Framework_MockObject_MockObject $deploymentConfigMock */ $deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) ->disableOriginalConstructor() ->getMock(); $deploymentConfigMock->expects($this->once()) ->method('get') - ->with(Config::PARAM_SESSION_SAVE_METHOD, SaveHandlerInterface::DEFAULT_HANDLER) + ->with(Config::PARAM_SESSION_SAVE_METHOD, $defaultHandler) ->willReturn($deploymentConfigHandler ?: SaveHandlerInterface::DEFAULT_HANDLER); new SaveHandler( @@ -85,4 +73,31 @@ class SaveHandlerTest extends \PHPUnit_Framework_TestCase [false, false], ]; } + + /** + * Retrieve expected session.save_handler + * + * @param string $deploymentConfigHandler + * @param string $iniHandler + * @return string + */ + private function getExpectedSaveHandler($deploymentConfigHandler, $iniHandler) + { + // Set expected session.save_handler config + if ($deploymentConfigHandler) { + if ($deploymentConfigHandler !== 'files') { + $expected = 'user'; + return $expected; + } else { + $expected = $deploymentConfigHandler; + return $expected; + } + } elseif ($iniHandler) { + $expected = $iniHandler; + return $expected; + } else { + $expected = SaveHandlerInterface::DEFAULT_HANDLER; + return $expected; + } + } } diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Block/Adminhtml/Export/Edit/FormTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Block/Adminhtml/Export/Edit/FormTest.php index 52fbe5ed7b6afbbd85f65aa864c453e46810facb..a463a426dd647cde6d352be51f63bd82a59db5d1 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Block/Adminhtml/Export/Edit/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Block/Adminhtml/Export/Edit/FormTest.php @@ -30,7 +30,11 @@ class FormTest extends \PHPUnit_Framework_TestCase * * @var array */ - protected $_expectedFields = ['base_fieldset' => ['entity' => 'entity', 'file_format' => 'file_format']]; + protected $_expectedFields = ['base_fieldset' => [ + 'entity' => 'entity', + 'file_format' => 'file_format', + 'fields_enclosure' => 'fields_enclosure' + ]]; protected function setUp() { diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ExportTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ExportTest.php index 9decf617d50ae1ca0b8c895b3d45e5c255d846cd..de9379d1494d3925590f228ea52c69934fd9bd8b 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ExportTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/ExportTest.php @@ -84,6 +84,6 @@ class ExportTest extends \Magento\TestFramework\TestCase\AbstractBackendControll $body = $this->getResponse()->getBody(); $this->assertSelectCount('fieldset#base_fieldset', 1, $body); - $this->assertSelectCount('fieldset#base_fieldset div.field', 2, $body); + $this->assertSelectCount('fieldset#base_fieldset div.field', 3, $body); } } diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates.php new file mode 100644 index 0000000000000000000000000000000000000000..5cac40809565e1ed28df9529446550975b39f9f3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$data = + [ + 'website_id' => 1, + 'dest_country_id' => 'US', + 'dest_region_id' => 0, + 'dest_zip' => '*', + 'condition_name' => 'package_qty', + 'condition_value' => 1, + 'price' => 10, + 'cost' => 10 + ]; +$connection->query( + "INSERT INTO {$entityTable} (`website_id`, `dest_country_id`, `dest_region_id`, `dest_zip`, `condition_name`," + . "`condition_value`, `price`, `cost`) VALUES (:website_id, :dest_country_id, :dest_region_id, :dest_zip," + . " :condition_name, :condition_value, :price, :cost);", + $data +); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_rollback.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..10f5563eee8aae9c826a8967e93658b59d9c2eca --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_rollback.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$connection->query("DELETE FROM {$entityTable};"); diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7ed966d0c9b998a23145bde4f2432019628a3c6f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Quote\Model; + +/** + * Class ShippingMethodManagementTest + */ +class ShippingMethodManagementTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoConfigFixture current_store carriers/tablerate/active 1 + * @magentoConfigFixture current_store carriers/tablerate/condition_name package_qty + * @magentoDataFixture Magento/SalesRule/_files/cart_rule_free_shipping.php + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/OfflineShipping/_files/tablerates.php + */ + public function testEstimateByAddressWithCartPriceRule() + { + $this->executeTestFlow(0, 0); + } + + /** + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoConfigFixture current_store carriers/tablerate/active 1 + * @magentoConfigFixture current_store carriers/tablerate/condition_name package_qty + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/OfflineShipping/_files/tablerates.php + */ + public function testEstimateByAddress() + { + $this->executeTestFlow(5, 10); + } + + /** + * Provide testing of shipping method estimation based on address + * + * @param int $flatRateAmount + * @param int $tableRateAmount + */ + private function executeTestFlow($flatRateAmount, $tableRateAmount) + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $objectManager->get(\Magento\Quote\Model\Quote::class); + $quote->load('test01', 'reserved_order_id'); + $cartId = $quote->getId(); + if (!$cartId) { + $this->fail('quote fixture failed'); + } + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); + $quoteIdMask->load($cartId, 'quote_id'); + //Use masked cart Id + $cartId = $quoteIdMask->getMaskedId(); + $data = [ + 'data' => [ + 'country_id' => "US", + 'postcode' => null, + 'region' => null, + 'region_id' => null + ] + ]; + /** @var \Magento\Quote\Api\Data\EstimateAddressInterface $address */ + $address = $objectManager->create(\Magento\Quote\Api\Data\EstimateAddressInterface::class, $data); + /** @var \Magento\Quote\Api\GuestShippingMethodManagementInterface $shippingEstimation */ + $shippingEstimation = $objectManager->get(\Magento\Quote\Api\GuestShippingMethodManagementInterface::class); + $result = $shippingEstimation->estimateByAddress($cartId, $address); + $this->assertNotEmpty($result); + $expectedResult = [ + 'tablerate' => + [ + 'method_code' => 'bestway', + 'amount' => $tableRateAmount + ], + 'flatrate' => [ + 'method_code' => 'flatrate', + 'amount' => $flatRateAmount + ] + ]; + foreach ($result as $rate) { + $this->assertEquals($expectedResult[$rate->getCarrierCode()]['amount'], $rate->getAmount()); + $this->assertEquals($expectedResult[$rate->getCarrierCode()]['method_code'], $rate->getMethodCode()); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..48cf991c848b067b7a3f631b13da812ab0de39b5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice_rollback.php @@ -0,0 +1,6 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +require 'default_rollback.php'; diff --git a/dev/tests/static/get_github_changes.php b/dev/tests/static/get_github_changes.php index c142aae18d8ecd5a4ab3fa895e32e5510b71683b..f332208cd17d0861dd1b28bd7096a0b3fe0066a5 100644 --- a/dev/tests/static/get_github_changes.php +++ b/dev/tests/static/get_github_changes.php @@ -12,9 +12,9 @@ // @codingStandardsIgnoreFile define( -'USAGE', -<<<USAGE - php -f get_github_changes.php -- + 'USAGE', + <<<USAGE + php -f get_github_changes.php -- --output-file="<output_file>" --base-path="<base_path>" --repo="<main_repo>" @@ -36,6 +36,8 @@ $fileExtensions = explode(',', isset($options['file-extensions']) ? $options['fi $mainline = 'mainline_' . (string)rand(0, 9999); $repo = getRepo($options, $mainline); +$branches = $repo->getBranches('--remotes'); +generateBranchesList($options['output-file'], $branches, $options['branch']); $changes = retrieveChangesAcrossForks($mainline, $repo, $options['branch']); $changedFiles = getChangedFiles($changes, $fileExtensions); generateChangedFilesList($options['output-file'], $changedFiles); @@ -57,6 +59,25 @@ function generateChangedFilesList($outputFile, $changedFiles) fclose($changedFilesList); } +/** + * Generates a file containing origin branches + * + * @param string $outputFile + * @param array $branches + * @param string $branchName + * @return void + */ +function generateBranchesList($outputFile, $branches, $branchName) +{ + $branchOutputFile = str_replace('changed_files', 'branches', $outputFile); + $branchesList = fopen($branchOutputFile, 'w'); + fwrite($branchesList, $branchName . PHP_EOL); + foreach ($branches as $branch) { + fwrite($branchesList, substr(strrchr($branch, '/'), 1) . PHP_EOL); + } + fclose($branchesList); +} + /** * Gets list of changed files * @@ -84,7 +105,7 @@ function getChangedFiles(array $changes, array $fileExtensions) * * @param array $options * @param string $mainline - * @return array + * @return GitRepo * @throws Exception */ function getRepo($options, $mainline) @@ -203,6 +224,19 @@ class GitRepo $this->call(sprintf('fetch %s', $remoteAlias)); } + /** + * Returns branches + * + * @param string $source + * @return array|mixed + */ + public function getBranches($source = '--all') + { + $result = $this->call(sprintf('branch ' . $source)); + + return is_array($result) ? $result : []; + } + /** * Returns files changes between branch and HEAD * diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt index 9ae1f7fc1452d3156aa7308ee3f81d194e99bbce..a50845d3884adf110ff8c36831e6ef8ac63670ca 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/exception_hierarchy.txt @@ -6,3 +6,4 @@ \Magento\Framework\DB\Adapter\ConnectionException \Magento\Framework\DB\Adapter\DeadlockException \Magento\Framework\DB\Adapter\LockWaitException +\Magento\Framework\DB\Adapter\DuplicateException diff --git a/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt b/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt index 77cf93864b0bd4e172da599e9ad854c7b14a5b0a..29b5280ec6693f70c8d65a1f40e25c644897ace1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt +++ b/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt @@ -41,13 +41,11 @@ app/code/Magento/Catalog/view/base/web/js/price-utils.js app/code/Magento/Catalog/view/base/web/js/tier-price.js app/code/Magento/Catalog/view/frontend/requirejs-config.js app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js -app/code/Magento/Catalog/view/frontend/web/js/compare.js app/code/Magento/Catalog/view/frontend/web/js/gallery.js app/code/Magento/Catalog/view/frontend/web/js/list.js app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js app/code/Magento/Catalog/view/frontend/web/js/related-products.js app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js -app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js app/code/Magento/Catalog/view/frontend/web/js/view/image.js app/code/Magento/Catalog/view/frontend/web/js/zoom.js app/code/Magento/Catalog/view/frontend/web/product/view/validation.js @@ -218,16 +216,16 @@ app/code/Magento/PageCache/view/frontend/web/js/page-cache.js app/code/Magento/Payment/view/adminhtml/web/transparent.js app/code/Magento/Payment/view/frontend/requirejs-config.js app/code/Magento/Payment/view/frontend/web/cc-type.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-data.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/cvv-validator.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js -app/code/Magento/Payment/view/frontend/web/js/model/credit-card-validation/validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-data.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/cvv-validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js +app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/validator.js app/code/Magento/Payment/view/frontend/web/js/view/payment/cc-form.js app/code/Magento/Payment/view/frontend/web/js/view/payment/iframe.js app/code/Magento/Payment/view/frontend/web/js/view/payment/method-renderer/free-method.js @@ -511,7 +509,6 @@ lib/web/mage/captcha.js lib/web/mage/collapsible.js lib/web/mage/common.js lib/web/mage/cookies.js -lib/web/mage/dataPost.js lib/web/mage/decorate.js lib/web/mage/deletable-item.js lib/web/mage/dialog.js @@ -603,13 +600,11 @@ vendor/magento/module-catalog/view/base/web/js/price-utils.js vendor/magento/module-catalog/view/base/web/js/tier-price.js vendor/magento/module-catalog/view/frontend/requirejs-config.js vendor/magento/module-catalog/view/frontend/web/js/catalog-add-to-cart.js -vendor/magento/module-catalog/view/frontend/web/js/compare.js vendor/magento/module-catalog/view/frontend/web/js/gallery.js vendor/magento/module-catalog/view/frontend/web/js/list.js vendor/magento/module-catalog/view/frontend/web/js/product/list/toolbar.js vendor/magento/module-catalog/view/frontend/web/js/related-products.js vendor/magento/module-catalog/view/frontend/web/js/upsell-products.js -vendor/magento/module-catalog/view/frontend/web/js/view/compare-products.js vendor/magento/module-catalog/view/frontend/web/js/view/image.js vendor/magento/module-catalog/view/frontend/web/js/zoom.js vendor/magento/module-catalog/view/frontend/web/product/view/validation.js @@ -776,16 +771,16 @@ vendor/magento/module-page-cache/view/frontend/web/js/page-cache.js vendor/magento/module-payment/view/adminhtml/web/transparent.js vendor/magento/module-payment/view/frontend/requirejs-config.js vendor/magento/module-payment/view/frontend/web/cc-type.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/credit-card-data.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/cvv-validator.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js -vendor/magento/module-payment/view/frontend/web/js/model/credit-card-validation/validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/credit-card-data.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/luhn10-validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/cvv-validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/expiration-date-validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-month-validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/expiration-year-validator.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/expiration-date-validator/parse-date.js +vendor/magento/module-payment/view/base/web/js/model/credit-card-validation/validator.js vendor/magento/module-payment/view/frontend/web/js/view/payment/cc-form.js vendor/magento/module-payment/view/frontend/web/js/view/payment/iframe.js vendor/magento/module-payment/view/frontend/web/js/view/payment/method-renderer/free-method.js diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php index 4029454ee523052fedcc9592b778fea9704b7abf..2f3d312fb41ffba1287bafd324a64ac751316d5c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php @@ -15,18 +15,46 @@ class ModuleDBChangeTest extends \PHPUnit_Framework_TestCase /** * @var string */ - protected static $changedFilesPattern = __DIR__ . '/../_files/changed_files*'; + private static $branchesFilesPattern = __DIR__ . '/../_files/branches*'; /** * @var string */ - protected static $changedFileList = ''; + private static $changedFilesPattern = __DIR__ . '/../_files/changed_files*'; + + /** + * @var string + */ + private static $changedFileList = ''; + + /** + * @var bool + */ + private static $actualBranch = false; /** * Set changed files paths and list for all projects */ public static function setUpBeforeClass() { + foreach (glob(self::$branchesFilesPattern) as $branchesFile) { + //get the current branchname from the first line + $branchName = trim(file($branchesFile)[0]); + if ($branchName === 'develop') { + self::$actualBranch = true; + } else { + //get current minor branch name + preg_match('|^(\d+\.\d+)|', $branchName, $minorBranch); + $branchName = $minorBranch[0]; + + //get all version branches + preg_match_all('|^(\d+\.\d+)|m', file_get_contents($branchesFile), $matches); + + //check is this a latest release branch + self::$actualBranch = ($branchName == max($matches[0])); + } + } + foreach (glob(self::$changedFilesPattern) as $changedFile) { self::$changedFileList .= file_get_contents($changedFile) . PHP_EOL; } @@ -37,12 +65,14 @@ class ModuleDBChangeTest extends \PHPUnit_Framework_TestCase */ public function testModuleXmlFiles() { - preg_match_all('|etc/module\.xml$|mi', self::$changedFileList, $matches); - $this->assertEmpty( - reset($matches), - 'module.xml changes for patch releases in non-actual branches are not allowed:' . PHP_EOL . - implode(PHP_EOL, array_values(reset($matches))) - ); + if (!self::$actualBranch) { + preg_match_all('|etc/module\.xml$|mi', self::$changedFileList, $matches); + $this->assertEmpty( + reset($matches), + 'module.xml changes for patch releases in non-actual branches are not allowed:' . PHP_EOL . + implode(PHP_EOL, array_values(reset($matches))) + ); + } } /** @@ -50,11 +80,13 @@ class ModuleDBChangeTest extends \PHPUnit_Framework_TestCase */ public function testModuleSetupFiles() { - preg_match_all('|app/code/Magento/[^/]+/Setup/[^/]+$|mi', self::$changedFileList, $matches); - $this->assertEmpty( - reset($matches), - 'Code with changes for DB schema or data in non-actual branches are not allowed:' . PHP_EOL . - implode(PHP_EOL, array_values(reset($matches))) - ); + if (!self::$actualBranch) { + preg_match_all('|app/code/Magento/[^/]+/Setup/[^/]+$|mi', self::$changedFileList, $matches); + $this->assertEmpty( + reset($matches), + 'Code with changes for DB schema or data in non-actual branches are not allowed:' . PHP_EOL . + implode(PHP_EOL, array_values(reset($matches))) + ); + } } } diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/UnsecureFunctionsUsageTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/UnsecureFunctionsUsageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6b18eb2b71478463e39e815dec078478c4034a07 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/UnsecureFunctionsUsageTest.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Test\Legacy; + +use Magento\Framework\App\Utility\Files; + +/** + * Tests to detect unsecure functions usage + */ +class UnsecureFunctionsUsageTest extends \PHPUnit_Framework_TestCase +{ + /** + * File extensions pattern to search for + * + * @var string + */ + private $fileExtensions = '/\.(php|phtml|js)$/'; + + /** + * Php unsecure functions to detect + * + * @var array + */ + private $phpUnsecureFunctions = [ + 'unserialize', + 'serialize', + 'eval', + 'md5', + 'srand', + 'mt_srand' + ]; + + /** + * JS unsecure functions to detect + * + * @var array + */ + private $jsUnsecureFunctions = []; + + /** + * Detect unsecure functions usage for changed files in whitelist with the exception of blacklist + * + * @return void + */ + public function testUnsecureFunctionsUsage() + { + $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); + $invoker( + function ($fileName) { + $result = ''; + $errorMessage = 'The following functions are non secure and should be avoided: ' + . implode(', ', $this->phpUnsecureFunctions) + . ' for PHP'; + if (!empty($this->jsUnsecureFunctions)) { + $errorMessage .= ', and ' + . implode(', ', $this->jsUnsecureFunctions) + . ' for JavaScript'; + } + $errorMessage .= ".\n"; + $regexp = $this->getRegexpByFileExtension(pathinfo($fileName, PATHINFO_EXTENSION)); + if ($regexp) { + $matches = preg_grep( + $regexp, + file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) + ); + if (!empty($matches)) { + foreach (array_keys($matches) as $line) { + $result .= $fileName . ':' . ($line + 1) . "\n"; + } + } + $this->assertEmpty($result, $errorMessage . $result); + } + }, + $this->unsecureFunctionsUsageDataProvider() + ); + } + + /** + * Data provider for test + * + * @return array + */ + public function unsecureFunctionsUsageDataProvider() + { + $fileExtensions = $this->fileExtensions; + $directoriesToScan = Files::init()->readLists(__DIR__ . '/_files/security/whitelist.txt'); + $blackListFiles = include __DIR__ . '/_files/security/blacklist.php'; + + $filesToVerify = []; + foreach (glob(__DIR__ . '/../_files/changed_files*') as $listFile) { + $filesToVerify = array_merge( + $filesToVerify, + file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) + ); + } + array_walk( + $filesToVerify, + function (&$file) { + $file = [BP . '/' . $file]; + } + ); + $filesToVerify = array_filter( + $filesToVerify, + function ($path) use ($directoriesToScan, $fileExtensions, $blackListFiles) { + if (!file_exists($path[0])) { + return false; + } + $path = realpath($path[0]); + foreach ($directoriesToScan as $directory) { + $directory = realpath($directory); + if (strpos($path, $directory) === 0) { + if (preg_match($fileExtensions, $path)) { + foreach ($blackListFiles as $blackListFile) { + if (preg_match($blackListFile, $path)) { + return false; + } + } + return true; + } + } + } + return false; + } + ); + return $filesToVerify; + } + + /** + * Get regular expression by file extension + * + * @param string $fileExtension + * @return string|bool + */ + private function getRegexpByFileExtension($fileExtension) + { + $regexp = false; + if ($fileExtension == 'php') { + $regexp = $this->prepareRegexp($this->phpUnsecureFunctions); + } elseif ($fileExtension == 'js') { + $regexp = $this->prepareRegexp($this->jsUnsecureFunctions); + } elseif ($fileExtension == 'phtml') { + $regexp = $this->prepareRegexp($this->phpUnsecureFunctions + $this->jsUnsecureFunctions); + } + return $regexp; + } + + /** + * Prepare regular expression for unsecure function names + * + * @param array $functions + * @return string + */ + private function prepareRegexp(array $functions) + { + if (empty($functions)) { + return ''; + } + $regexArray = []; + foreach ($functions as $function) { + $regexArray[] = '\b' . $function . '\b\('; + } + return '/' . implode('|', $regexArray) . '/i'; + } +} 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 78342c886de968a02ceed79c215c5dc26ddd9928..f5ae2f2dfd67b5628326149d6d28f055c9d2485f 100755 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -785,6 +785,7 @@ return [ ['Mage_Core_Model_Config_Fieldset', 'Magento\Core\Model\Fieldset\Config'], ['Mage_Core_Model_Config_Options', 'Magento\Framework\Filesystem'], ['Magento\Framework\App\Dir', 'Magento\Framework\Filesystem'], + ['Magento\Framework\EntityManager\CustomAttributesMapper'], ['Magento\Framework\Filesystem\Adapter\Local', 'Magento\Framework\Filesystem\Driver\File'], ['Magento\Framework\Filesystem\Adapter\Zlib', 'Magento\Framework\Filesystem\Driver\Zlib'], ['Magento\Framework\Filesystem\AdapterInterface'], diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/blacklist.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/blacklist.php new file mode 100644 index 0000000000000000000000000000000000000000..d120a4543b9ddced597d330ab489de15c80cdb86 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/blacklist.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +return [ + '/Test\/Unit/', + '/lib\/internal\/Magento\/Framework\/DB\/Adapter\/Pdo\/Mysql\.php/', +]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/whitelist.txt b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/whitelist.txt new file mode 100644 index 0000000000000000000000000000000000000000..2567475de6a035b2a369ac342c076ce1e5cad07d --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/whitelist.txt @@ -0,0 +1,4 @@ +module * / +library * / +setup +pub \ No newline at end of file diff --git a/lib/internal/Magento/Framework/App/Cache/Type/Dummy.php b/lib/internal/Magento/Framework/App/Cache/Type/Dummy.php new file mode 100644 index 0000000000000000000000000000000000000000..ea6a3c3e92e7e615740990170d218608aff99836 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Cache/Type/Dummy.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Cache\Type; + +use Magento\Framework\App\CacheInterface; + +/** + * Dummy cache adapter + * + * for cases when need to disable interaction with cache + * but no specific cache type is used + */ +class Dummy implements CacheInterface +{ + /** + * Required by CacheInterface + * + * @return null + */ + public function getFrontend() + { + return null; + } + + /** + * Pretend to load data from cache by id + * + * {@inheritdoc} + */ + public function load($identifier) + { + return null; + } + + /** + * Pretend to save data + * + * {@inheritdoc} + */ + public function save($data, $identifier, $tags = [], $lifeTime = null) + { + return false; + } + + /** + * Pretend to remove cached data by identifier + * + * {@inheritdoc} + */ + public function remove($identifier) + { + return true; + } + + /** + * Pretend to clean cached data by specific tag + * + * {@inheritdoc} + */ + public function clean($tags = []) + { + return true; + } +} diff --git a/lib/internal/Magento/Framework/App/Test/Unit/View/Deployment/VersionTest.php b/lib/internal/Magento/Framework/App/Test/Unit/View/Deployment/VersionTest.php index 30f398b4f6000bdd2824ed19b8c625a6e4e21566..187c043945d059992d6dc59a8713fc413b1a9752 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/View/Deployment/VersionTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/View/Deployment/VersionTest.php @@ -5,7 +5,8 @@ */ namespace Magento\Framework\App\Test\Unit\View\Deployment; -use \Magento\Framework\App\View\Deployment\Version; +use Magento\Framework\App\View\Deployment\Version; +use Magento\Framework\Exception\FileSystemException; /** * Class VersionTest @@ -18,29 +19,39 @@ class VersionTest extends \PHPUnit_Framework_TestCase private $object; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\State|\PHPUnit_Framework_MockObject_MockObject */ - private $appState; + private $appStateMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\View\Deployment\Version\StorageInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $versionStorage; + private $versionStorageMock; + + /** + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; protected function setUp() { - $this->appState = $this->getMock(\Magento\Framework\App\State::class, [], [], '', false); - $this->versionStorage = $this->getMock(\Magento\Framework\App\View\Deployment\Version\StorageInterface::class); - $this->object = new Version($this->appState, $this->versionStorage); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->appStateMock = $this->getMock(\Magento\Framework\App\State::class, [], [], '', false); + $this->versionStorageMock = $this->getMock( + \Magento\Framework\App\View\Deployment\Version\StorageInterface::class + ); + $this->loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $this->object = new Version($this->appStateMock, $this->versionStorageMock); + $objectManager->setBackwardCompatibleProperty($this->object, 'logger', $this->loggerMock); } public function testGetValueDeveloperMode() { - $this->appState + $this->appStateMock ->expects($this->once()) ->method('getMode') ->will($this->returnValue(\Magento\Framework\App\State::MODE_DEVELOPER)); - $this->versionStorage->expects($this->never())->method($this->anything()); + $this->versionStorageMock->expects($this->never())->method($this->anything()); $this->assertInternalType('integer', $this->object->getValue()); $this->object->getValue(); // Ensure computation occurs only once and result is cached in memory } @@ -51,12 +62,12 @@ class VersionTest extends \PHPUnit_Framework_TestCase */ public function testGetValueFromStorage($appMode) { - $this->appState + $this->appStateMock ->expects($this->once()) ->method('getMode') ->will($this->returnValue($appMode)); - $this->versionStorage->expects($this->once())->method('load')->will($this->returnValue('123')); - $this->versionStorage->expects($this->never())->method('save'); + $this->versionStorageMock->expects($this->once())->method('load')->will($this->returnValue('123')); + $this->versionStorageMock->expects($this->never())->method('save'); $this->assertEquals('123', $this->object->getValue()); $this->object->getValue(); // Ensure caching in memory } @@ -70,20 +81,106 @@ class VersionTest extends \PHPUnit_Framework_TestCase ]; } - public function testGetValueDefaultModeSaving() - { + /** + * $param bool $isUnexpectedValueExceptionThrown + * $param bool $isFileSystemExceptionThrown + * @dataProvider getValueDefaultModeDataProvider + */ + public function testGetValueDefaultMode( + $isUnexpectedValueExceptionThrown, + $isFileSystemExceptionThrown = null + ) { $versionType = 'integer'; - $this->appState + $this->appStateMock ->expects($this->once()) ->method('getMode') - ->will($this->returnValue(\Magento\Framework\App\State::MODE_DEFAULT)); - $storageException = new \UnexpectedValueException('Does not exist in the storage'); - $this->versionStorage - ->expects($this->once()) - ->method('load') - ->will($this->throwException($storageException)); - $this->versionStorage->expects($this->once())->method('save')->with($this->isType($versionType)); + ->willReturn(\Magento\Framework\App\State::MODE_DEFAULT); + if ($isUnexpectedValueExceptionThrown) { + $storageException = new \UnexpectedValueException('Does not exist in the storage'); + $this->versionStorageMock + ->expects($this->once()) + ->method('load') + ->will($this->throwException($storageException)); + $this->versionStorageMock->expects($this->once()) + ->method('save') + ->with($this->isType($versionType)); + if ($isFileSystemExceptionThrown) { + $fileSystemException = new FileSystemException( + new \Magento\Framework\Phrase('Can not load static content version') + ); + $this->versionStorageMock + ->expects($this->once()) + ->method('save') + ->will($this->throwException($fileSystemException)); + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with('Can not save static content version.'); + } else { + $this->loggerMock->expects($this->never()) + ->method('critical'); + } + } else { + $this->versionStorageMock + ->expects($this->once()) + ->method('load') + ->willReturn(1475779229); + $this->loggerMock->expects($this->never()) + ->method('critical'); + } $this->assertInternalType($versionType, $this->object->getValue()); - $this->object->getValue(); // Ensure caching in memory + $this->object->getValue(); + } + + /** + * @return array + */ + public function getValueDefaultModeDataProvider() + { + return [ + [false], + [true, false], + [true, true] + ]; + } + + /** + * @param bool $isUnexpectedValueExceptionThrown + * @dataProvider getValueProductionModeDataProvider + */ + public function testGetValueProductionMode( + $isUnexpectedValueExceptionThrown + ) { + $this->appStateMock + ->expects($this->once()) + ->method('getMode') + ->willReturn(\Magento\Framework\App\State::MODE_PRODUCTION); + if ($isUnexpectedValueExceptionThrown) { + $storageException = new \UnexpectedValueException('Does not exist in the storage'); + $this->versionStorageMock + ->expects($this->once()) + ->method('load') + ->will($this->throwException($storageException)); + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with('Can not load static content version.'); + } else { + $this->versionStorageMock + ->expects($this->once()) + ->method('load') + ->willReturn(1475779229); + } + $this->assertInternalType('integer', $this->object->getValue()); + $this->object->getValue(); + } + + /** + * @return array + */ + public function getValueProductionModeDataProvider() + { + return [ + [false], + [true], + ]; } } diff --git a/lib/internal/Magento/Framework/App/View/Deployment/Version.php b/lib/internal/Magento/Framework/App/View/Deployment/Version.php index 8e927e47921a0c428493de2bb6e13b17407483ad..7f6dc7fa9285ccc3987d84091bc9edb76d09f794 100644 --- a/lib/internal/Magento/Framework/App/View/Deployment/Version.php +++ b/lib/internal/Magento/Framework/App/View/Deployment/Version.php @@ -6,6 +6,9 @@ namespace Magento\Framework\App\View\Deployment; +use Psr\Log\LoggerInterface; +use Magento\Framework\Exception\FileSystemException; + /** * Deployment version of static files */ @@ -26,6 +29,11 @@ class Version */ private $cachedValue; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param \Magento\Framework\App\State $appState * @param Version\StorageInterface $versionStorage @@ -59,23 +67,49 @@ class Version */ protected function readValue($appMode) { - switch ($appMode) { - case \Magento\Framework\App\State::MODE_DEFAULT: - try { - $result = $this->versionStorage->load(); - } catch (\UnexpectedValueException $e) { - $result = (new \DateTime())->getTimestamp(); - $this->versionStorage->save($result); + if ($appMode == \Magento\Framework\App\State::MODE_DEVELOPER) { + $result = $this->generateVersion(); + } else { + try { + $result = $this->versionStorage->load(); + } catch (\UnexpectedValueException $e) { + $result = $this->generateVersion(); + if ($appMode == \Magento\Framework\App\State::MODE_DEFAULT) { + try { + $this->versionStorage->save($result); + } catch (FileSystemException $e) { + $this->getLogger()->critical('Can not save static content version.'); + } + } else { + $this->getLogger()->critical('Can not load static content version.'); } - break; + } + } + return $result; + } - case \Magento\Framework\App\State::MODE_DEVELOPER: - $result = (new \DateTime())->getTimestamp(); - break; + /** + * Generate version of static content + * + * @return int + */ + private function generateVersion() + { + return time(); + } - default: - $result = $this->versionStorage->load(); + /** + * Get logger + * + * @return LoggerInterface + * @deprecated + */ + private function getLogger() + { + if ($this->logger == null) { + $this->logger = \Magento\Framework\App\ObjectManager::getInstance() + ->get(LoggerInterface::class); } - return $result; + return $this->logger; } } diff --git a/lib/internal/Magento/Framework/Backup/Filesystem.php b/lib/internal/Magento/Framework/Backup/Filesystem.php index 2d77fa164b0a4ec228288a011de95e3c3ea5dee8..7fe469441eeaf6bd53a5144a5180669ff47b4583 100644 --- a/lib/internal/Magento/Framework/Backup/Filesystem.php +++ b/lib/internal/Magento/Framework/Backup/Filesystem.php @@ -316,7 +316,7 @@ class Filesystem extends AbstractBackup if (!$this->rollBackFtp) { $this->rollBackFtp = ObjectManager::getInstance()->create( \Magento\Framework\Backup\Filesystem\Rollback\Ftp::class, - [$this] + ['snapshotObject' => $this] ); } @@ -332,7 +332,7 @@ class Filesystem extends AbstractBackup if (!$this->rollBackFs) { $this->rollBackFs = ObjectManager::getInstance()->create( \Magento\Framework\Backup\Filesystem\Rollback\Fs::class, - [$this] + ['snapshotObject' => $this] ); } diff --git a/lib/internal/Magento/Framework/DB/Adapter/DuplicateException.php b/lib/internal/Magento/Framework/DB/Adapter/DuplicateException.php new file mode 100644 index 0000000000000000000000000000000000000000..06c4dfcc30ab26572b0faae1d90d1939989455ba --- /dev/null +++ b/lib/internal/Magento/Framework/DB/Adapter/DuplicateException.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\DB\Adapter; + +/** + * Database duplicate exception + */ +class DuplicateException extends \Zend_Db_Adapter_Exception +{ +} diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 6399824590a696980b641abcd3d0fc5b4e09cf42..cfbeb4f54c9e29d2f7b320b57c51ae7a7b31a676 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -14,6 +14,7 @@ use Magento\Framework\Cache\FrontendInterface; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Adapter\ConnectionException; use Magento\Framework\DB\Adapter\DeadlockException; +use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\DB\Adapter\LockWaitException; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\DB\ExpressionConverter; @@ -232,6 +233,8 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface 1205 => LockWaitException::class, // SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock 1213 => DeadlockException::class, + // SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry + 1062 => DuplicateException::class, ]; try { parent::__construct($config); @@ -465,7 +468,7 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface * @param mixed $bind An array of data or data itself to bind to the placeholders. * @return \Zend_Db_Statement_Pdo|void * @throws \Zend_Db_Adapter_Exception To re-throw \PDOException. - * @throws LocalizedException In case multiple queries are attempted at once, to protect from SQL injection + * @throws \Zend_Db_Statement_Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _query($sql, $bind = []) diff --git a/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php b/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php index 74a6cc7d2cd923e46b80f39c2645c5cef2ba421a..d714caa1724f07c3aab05de714048e8ec72a6a80 100644 --- a/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php +++ b/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php @@ -54,25 +54,70 @@ class ArrayType implements InterpreterInterface */ private function sortItems($items) { - uasort( - $items, - function ($firstItem, $secondItem) { - $firstValue = 0; - $secondValue = 0; - if (isset($firstItem['sortOrder'])) { - $firstValue = intval($firstItem['sortOrder']); + $sortOrderDefined = $this->isSortOrderDefined($items); + if ($sortOrderDefined) { + $indexedItems = []; + foreach ($items as $key => $item) { + $indexedItems[] = ['key' => $key, 'item' => $item]; + } + uksort( + $indexedItems, + function ($firstItemKey, $secondItemKey) use ($indexedItems) { + return $this->compareItems($firstItemKey, $secondItemKey, $indexedItems); } + ); + // Convert array of sorted items back to initial format + $items = []; + foreach ($indexedItems as $indexedItem) { + $items[$indexedItem['key']] = $indexedItem['item']; + } + } + return $items; + } - if (isset($secondItem['sortOrder'])) { - $secondValue = intval($secondItem['sortOrder']); - } + /** + * Compare sortOrder of item + * + * @param mixed $firstItemKey + * @param mixed $secondItemKey + * @param array $indexedItems + * @return int + */ + private function compareItems($firstItemKey, $secondItemKey, $indexedItems) + { + $firstItem = $indexedItems[$firstItemKey]['item']; + $secondItem = $indexedItems[$secondItemKey]['item']; + $firstValue = 0; + $secondValue = 0; + if (isset($firstItem['sortOrder'])) { + $firstValue = intval($firstItem['sortOrder']); + } - if ($firstValue == $secondValue) { - return 0; - } - return $firstValue < $secondValue ? -1 : 1; + if (isset($secondItem['sortOrder'])) { + $secondValue = intval($secondItem['sortOrder']); + } + + if ($firstValue == $secondValue) { + // These keys reflect initial relative position of items. + // Allows stable sort for items with equal 'sortOrder' + return $firstItemKey < $secondItemKey ? -1 : 1; + } + return $firstValue < $secondValue ? -1 : 1; + } + + /** + * Determine if a sort order exists for any of the items. + * + * @param array $items + * @return bool + */ + private function isSortOrderDefined($items) + { + foreach ($items as $itemData) { + if (isset($itemData['sortOrder'])) { + return true; } - ); - return $items; + } + return false; } } diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Argument/Interpreter/ArrayTypeTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Argument/Interpreter/ArrayTypeTest.php index 7d3c21ba271cecf75a83d4bc9aa5923ce1920337..e1bc7528cbe1a00a0d5abebb2739cf9a83c7f43a 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Argument/Interpreter/ArrayTypeTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Argument/Interpreter/ArrayTypeTest.php @@ -103,6 +103,40 @@ class ArrayTypeTest extends \PHPUnit_Framework_TestCase 'key1' => '-value 1-', ], ], + 'pre-sorted array items' => [ + [ + 'item' => [ + 'key1' => ['value' => 'value 1'], + 'key4' => ['value' => 'value 4'], + 'key2' => ['value' => 'value 2', 'sortOrder' => 10], + 'key3' => ['value' => 'value 3'], + ], + ], + [ + 'key1' => '-value 1-', + 'key4' => '-value 4-', + 'key3' => '-value 3-', + 'key2' => '-value 2-', + ], + ], + 'sort order edge case values' => [ + [ + 'item' => [ + 'key1' => ['value' => 'value 1', 'sortOrder' => 101], + 'key4' => ['value' => 'value 4'], + 'key2' => ['value' => 'value 2', 'sortOrder' => -10], + 'key3' => ['value' => 'value 3'], + 'key5' => ['value' => 'value 5', 'sortOrder' => 20], + ], + ], + [ + 'key2' => '-value 2-', + 'key4' => '-value 4-', + 'key3' => '-value 3-', + 'key5' => '-value 5-', + 'key1' => '-value 1-', + ], + ], ]; } } diff --git a/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php b/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php deleted file mode 100644 index 70cb79f950f28c7f0cefeb98d5e9a278316cccf2..0000000000000000000000000000000000000000 --- a/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\EntityManager; - -/** - * Class CustomAttributesMapper - */ -class CustomAttributesMapper implements MapperInterface -{ - /** - * @var MapperInterface - */ - private $mapper; - - /** - * CustomAttributesMapper constructor. - * - * @param MapperInterface $mapper - */ - public function __construct(MapperInterface $mapper) - { - $this->mapper = $mapper; - } - - /** - * {@inheritdoc} - */ - public function entityToDatabase($entityType, $data) - { - return $this->mapper->entityToDatabase($entityType, $data); - } - - /** - * {@inheritdoc} - * @deprecated - */ - public function databaseToEntity($entityType, $data) - { - return $this->mapper->databaseToEntity($entityType, $data); - } -} diff --git a/lib/internal/Magento/Framework/EntityManager/Db/CreateRow.php b/lib/internal/Magento/Framework/EntityManager/Db/CreateRow.php index f4362e4d4a210defe41a380b30516c93130e6751..5eeb1d948ba39b7e8a1a8e3c942eb8a21009cadf 100644 --- a/lib/internal/Magento/Framework/EntityManager/Db/CreateRow.php +++ b/lib/internal/Magento/Framework/EntityManager/Db/CreateRow.php @@ -50,11 +50,12 @@ class CreateRow { $output = []; foreach ($connection->describeTable($metadata->getEntityTable()) as $column) { - - if ($column['DEFAULT'] == 'CURRENT_TIMESTAMP') { + $columnName = strtolower($column['COLUMN_NAME']); + if ($this->canNotSetTimeStamp($columnName, $column, $data)) { continue; } - if (isset($data[strtolower($column['COLUMN_NAME'])])) { + + if (isset($data[$columnName])) { $output[strtolower($column['COLUMN_NAME'])] = $data[strtolower($column['COLUMN_NAME'])]; } elseif ($column['DEFAULT'] === null) { $output[strtolower($column['COLUMN_NAME'])] = null; @@ -66,6 +67,18 @@ class CreateRow return $output; } + /** + * @param string $columnName + * @param string $column + * @param array $data + * @return bool + */ + private function canNotSetTimeStamp($columnName, $column, array $data) + { + return $column['DEFAULT'] == 'CURRENT_TIMESTAMP' && !isset($data[$columnName]) + && empty($column['NULLABLE']); + } + /** * @param string $entityType * @param array $data diff --git a/lib/internal/Magento/Framework/EntityManager/Db/UpdateRow.php b/lib/internal/Magento/Framework/EntityManager/Db/UpdateRow.php index 3f65774cf10a615d7cfd95b30b06b0fb1ccd4a1e..0c9189261bc45e1b427fc2bffcdde71d27fc8571 100644 --- a/lib/internal/Magento/Framework/EntityManager/Db/UpdateRow.php +++ b/lib/internal/Magento/Framework/EntityManager/Db/UpdateRow.php @@ -50,11 +50,12 @@ class UpdateRow { $output = []; foreach ($connection->describeTable($metadata->getEntityTable()) as $column) { - if ($column['DEFAULT'] == 'CURRENT_TIMESTAMP' || $column['IDENTITY']) { + $columnName = strtolower($column['COLUMN_NAME']); + if ($this->canNotSetTimeStamp($columnName, $column, $data) || $column['IDENTITY']) { continue; } - if (isset($data[strtolower($column['COLUMN_NAME'])])) { + if (isset($data[$columnName])) { $output[strtolower($column['COLUMN_NAME'])] = $data[strtolower($column['COLUMN_NAME'])]; } elseif (!empty($column['NULLABLE'])) { $output[strtolower($column['COLUMN_NAME'])] = null; @@ -67,6 +68,18 @@ class UpdateRow return $output; } + /** + * @param string $columnName + * @param string $column + * @param array $data + * @return bool + */ + private function canNotSetTimeStamp($columnName, $column, array $data) + { + return $column['DEFAULT'] == 'CURRENT_TIMESTAMP' && !isset($data[$columnName]) + && empty($column['NULLABLE']); + } + /** * @param string $entityType * @param array $data diff --git a/lib/internal/Magento/Framework/EntityManager/Observer/AfterEntityLoad.php b/lib/internal/Magento/Framework/EntityManager/Observer/AfterEntityLoad.php index 010cafdd2f7148156bd02b0f2c28db5e469c0f54..3de6bd8fbcdd55840c39d31bf449f1f3ae5c6486 100644 --- a/lib/internal/Magento/Framework/EntityManager/Observer/AfterEntityLoad.php +++ b/lib/internal/Magento/Framework/EntityManager/Observer/AfterEntityLoad.php @@ -32,6 +32,7 @@ class AfterEntityLoad implements ObserverInterface } $entity->getResource()->afterLoad($entity); $entity->afterLoad(); + $entity->setOrigData(); $entity->setHasDataChanges(false); } } diff --git a/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityLoad.php b/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityLoad.php new file mode 100644 index 0000000000000000000000000000000000000000..1d292a2e5b3f81516563336f11dbf195b93b64d1 --- /dev/null +++ b/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityLoad.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\EntityManager\Observer; + +use Magento\Framework\Event\Observer; +use Magento\Framework\Model\AbstractModel; + +/** + * Class BeforeEntityLoad + */ +class BeforeEntityLoad +{ + /** + * Apply model before load operation + * + * @param Observer $observer + * @throws \Magento\Framework\Validator\Exception + * @return void + */ + public function execute(Observer $observer) + { + $identifier = $observer->getEvent()->getIdentifier(); + $entity = $observer->getEvent()->getEntity(); + if ($entity instanceof AbstractModel) { + $entity->beforeLoad($identifier); + } + } +} diff --git a/lib/internal/Magento/Framework/EntityManager/Operation/Create.php b/lib/internal/Magento/Framework/EntityManager/Operation/Create.php index 7e3dfb2e753caa4ebd8efc72c159b0b3a3d521d4..552fe7bca86f58fb722fab1b08294b4ee41d3734 100644 --- a/lib/internal/Magento/Framework/EntityManager/Operation/Create.php +++ b/lib/internal/Magento/Framework/EntityManager/Operation/Create.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\EntityManager\Operation; -use Magento\Framework\EntityManager\Operation\CreateInterface; +use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\EntityManager\Operation\Create\CreateMain; use Magento\Framework\EntityManager\Operation\Create\CreateAttributes; use Magento\Framework\EntityManager\Operation\Create\CreateExtensions; @@ -13,6 +13,8 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\EventManager; use Magento\Framework\EntityManager\TypeResolver; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Framework\Phrase; /** * Class Create @@ -86,6 +88,7 @@ class Create implements CreateInterface * @param array $arguments * @return object * @throws \Exception + * @throws AlreadyExistsException */ public function execute($entity, $arguments = []) { @@ -114,6 +117,9 @@ class Create implements CreateInterface ] ); $connection->commit(); + } catch (DuplicateException $e) { + $connection->rollBack(); + throw new AlreadyExistsException(new Phrase('Unique constraint violation found'), $e); } catch (\Exception $e) { $connection->rollBack(); throw $e; diff --git a/lib/internal/Magento/Framework/EntityManager/Operation/Read.php b/lib/internal/Magento/Framework/EntityManager/Operation/Read.php index 1cd988f53a0050aeaffa1e55b96270a2eb84ce2c..19a3912890db5af5f50cfab964e0c2ca0de572c4 100644 --- a/lib/internal/Magento/Framework/EntityManager/Operation/Read.php +++ b/lib/internal/Magento/Framework/EntityManager/Operation/Read.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\EntityManager\Operation; -use Magento\Framework\EntityManager\Operation\ReadInterface; use Magento\Framework\EntityManager\Operation\Read\ReadMain; use Magento\Framework\EntityManager\Operation\Read\ReadAttributes; use Magento\Framework\EntityManager\Operation\Read\ReadExtensions; @@ -13,7 +12,6 @@ use Magento\Framework\EntityManager\HydratorPool; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\EventManager; use Magento\Framework\EntityManager\TypeResolver; -use Magento\Framework\App\ResourceConnection; /** * Class Read @@ -83,11 +81,7 @@ class Read implements ReadInterface } /** - * @param object $entity - * @param string $identifier - * @param array $arguments - * @return object - * @throws \Exception + * {@inheritDoc} */ public function execute($entity, $identifier, $arguments = []) { @@ -107,6 +101,7 @@ class Read implements ReadInterface 'load_before', [ 'identifier' => $identifier, + 'entity' => $entity, 'arguments' => $arguments ] ); diff --git a/lib/internal/Magento/Framework/EntityManager/Operation/Update.php b/lib/internal/Magento/Framework/EntityManager/Operation/Update.php index 22abb464f8cd9ad96713899ca95fc0a3dc87e782..dbc8dc63873498888a40396795f9d6a0b9edca19 100644 --- a/lib/internal/Magento/Framework/EntityManager/Operation/Update.php +++ b/lib/internal/Magento/Framework/EntityManager/Operation/Update.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\EntityManager\Operation; -use Magento\Framework\EntityManager\Operation\UpdateInterface; +use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\EntityManager\Operation\Update\UpdateMain; use Magento\Framework\EntityManager\Operation\Update\UpdateAttributes; use Magento\Framework\EntityManager\Operation\Update\UpdateExtensions; @@ -13,6 +13,8 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\EventManager; use Magento\Framework\EntityManager\TypeResolver; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Framework\Phrase; /** * Class Update @@ -114,6 +116,9 @@ class Update implements UpdateInterface ] ); $connection->commit(); + } catch (DuplicateException $e) { + $connection->rollBack(); + throw new AlreadyExistsException(new Phrase('Unique constraint violation found'), $e); } catch (\Exception $e) { $connection->rollBack(); throw $e; diff --git a/lib/internal/Magento/Framework/EntityManager/Test/Unit/Db/UpdateRowTest.php b/lib/internal/Magento/Framework/EntityManager/Test/Unit/Db/UpdateRowTest.php index b22334f4402b1597a7af220cfc168379cde449be..1379ca072bf91898c25a06a796b40f26f2fc2899 100644 --- a/lib/internal/Magento/Framework/EntityManager/Test/Unit/Db/UpdateRowTest.php +++ b/lib/internal/Magento/Framework/EntityManager/Test/Unit/Db/UpdateRowTest.php @@ -63,33 +63,14 @@ class UpdateRowTest extends \PHPUnit_Framework_TestCase ]); } - public function testExecute() + /** + * @dataProvider columnsDataProvider + * @param array $data + * @param array $columns + * @param array $preparedColumns + */ + public function testExecute(array $data, array $columns, array $preparedColumns) { - $data = [ - 'test_link_field' => 1, - 'identified_field' => 'test_identified_field', - 'test_simple' => 'test_value', - ]; - $columns = [ - 'test_nullable' => [ - 'NULLABLE' => true, - 'DEFAULT' => false, - 'IDENTITY' => false, - 'COLUMN_NAME' => 'test_nullable', - ], - 'test_simple' => [ - 'NULLABLE' => true, - 'DEFAULT' => false, - 'IDENTITY' => false, - 'COLUMN_NAME' => 'test_simple', - ], - ]; - $preparedColumns = [ - 'test_identified_field' => null, - 'test_nullable' => null, - 'test_simple' => 'test_value', - ]; - $this->metadataPoolMock->expects($this->once()) ->method('getMetadata') ->with('test') @@ -115,7 +96,78 @@ class UpdateRowTest extends \PHPUnit_Framework_TestCase $this->metadataMock->expects($this->exactly(2)) ->method('getIdentifierField') ->willReturn('test_identified_field'); - + if (empty($data['updated_at'])) { + unset($data['updated_at']); + } $this->assertSame($data, $this->model->execute('test', $data)); } + + /** + * @return array + */ + public function columnsDataProvider() + { + $data = [ + 'test_link_field' => 1, + 'identified_field' => 'test_identified_field', + 'test_simple' => 'test_value', + ]; + $columns = [ + 'test_nullable' => [ + 'NULLABLE' => true, + 'DEFAULT' => false, + 'IDENTITY' => false, + 'COLUMN_NAME' => 'test_nullable', + ], + 'test_simple' => [ + 'NULLABLE' => true, + 'DEFAULT' => false, + 'IDENTITY' => false, + 'COLUMN_NAME' => 'test_simple', + ], + ]; + $preparedColumns = [ + 'test_identified_field' => null, + 'test_nullable' => null, + 'test_simple' => 'test_value', + ]; + + return [ + 'default' => [ + 'data' => $data, + 'columns' => $columns, + 'preparedColumns' => $preparedColumns, + ], + 'empty timestamp field' => [ + 'data' => array_merge($data, ['updated_at' => '']), + 'columns' => array_merge( + $columns, + [ + 'updated_at' => [ + 'NULLABLE' => false, + 'DEFAULT' => 'CURRENT_TIMESTAMP', + 'IDENTITY' => false, + 'COLUMN_NAME' => 'updated_at', + ], + ] + ), + 'preparedColumns' => $preparedColumns, + ], + 'filled timestamp field' => [ + 'data' => array_merge($data, ['updated_at' => '2016-01-01 00:00:00']), + 'columns' => array_merge( + $columns, + [ + 'updated_at' => [ + 'NULLABLE' => false, + 'DEFAULT' => 'CURRENT_TIMESTAMP', + 'IDENTITY' => false, + 'COLUMN_NAME' => 'updated_at', + ], + ] + ), + 'preparedColumns' => array_merge($preparedColumns, ['updated_at' => '2016-01-01 00:00:00']), + ], + ]; + } } diff --git a/lib/internal/Magento/Framework/EntityManager/Test/Unit/Operation/CreateTest.php b/lib/internal/Magento/Framework/EntityManager/Test/Unit/Operation/CreateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e45e8ffac1934859b16fd040505865d2f078eed3 --- /dev/null +++ b/lib/internal/Magento/Framework/EntityManager/Test/Unit/Operation/CreateTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\EntityManager\Test\Unit\Operation; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DataObject; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\EntityManager\EntityMetadataInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\EntityManager\Operation\Create; +use Magento\Framework\EntityManager\Operation\Create\CreateMain; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class CreateTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPool; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnection; + + /** + * @var CreateMain|\PHPUnit_Framework_MockObject_MockObject + */ + private $createMain; + + /** + * @var Create + */ + private $create; + + public function setUp() + { + $this->metadataPool = $this->getMockBuilder(MetadataPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->createMain = $this->getMockBuilder(CreateMain::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->create = (new ObjectManager($this))->getObject(Create::class, [ + 'metadataPool' => $this->metadataPool, + 'resourceConnection' => $this->resourceConnection, + 'createMain' => $this->createMain, + ]); + } + + /** + * @expectedException \Magento\Framework\Exception\AlreadyExistsException + */ + public function testDuplicateExceptionProcessingOnExecute() + { + $metadata = $this->getMock(EntityMetadataInterface::class); + $this->metadataPool->expects($this->any())->method('getMetadata')->willReturn($metadata); + + $connection = $this->getMock(AdapterInterface::class); + $connection->expects($this->once())->method('rollback'); + $this->resourceConnection->expects($this->any())->method('getConnectionByName')->willReturn($connection); + + $this->createMain->expects($this->once())->method('execute')->willThrowException(new DuplicateException()); + + $entity = $this->getMockBuilder(DataObject::class) + ->disableOriginalConstructor() + ->getMock(); + $this->create->execute($entity); + } +} diff --git a/lib/internal/Magento/Framework/EntityManager/Test/Unit/Operation/UpdateTest.php b/lib/internal/Magento/Framework/EntityManager/Test/Unit/Operation/UpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2ec78c2560a28b3d0cd2043b8fa09fe653e49be8 --- /dev/null +++ b/lib/internal/Magento/Framework/EntityManager/Test/Unit/Operation/UpdateTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\EntityManager\Test\Unit\Operation; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DataObject; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\EntityManager\EntityMetadataInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\EntityManager\Operation\Update; +use Magento\Framework\EntityManager\Operation\Update\UpdateMain; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class UpdateTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPool; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnection; + + /** + * @var UpdateMain|\PHPUnit_Framework_MockObject_MockObject + */ + private $updateMain; + + /** + * @var Update + */ + private $update; + + public function setUp() + { + $this->metadataPool = $this->getMockBuilder(MetadataPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->updateMain = $this->getMockBuilder(UpdateMain::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->update = (new ObjectManager($this))->getObject(Update::class, [ + 'metadataPool' => $this->metadataPool, + 'resourceConnection' => $this->resourceConnection, + 'updateMain' => $this->updateMain, + ]); + } + + /** + * @expectedException \Magento\Framework\Exception\AlreadyExistsException + */ + public function testDuplicateExceptionProcessingOnExecute() + { + $metadata = $this->getMock(EntityMetadataInterface::class); + $this->metadataPool->expects($this->any())->method('getMetadata')->willReturn($metadata); + + $connection = $this->getMock(AdapterInterface::class); + $connection->expects($this->once())->method('rollback'); + $this->resourceConnection->expects($this->any())->method('getConnectionByName')->willReturn($connection); + + $this->updateMain->expects($this->once())->method('execute')->willThrowException(new DuplicateException()); + + $entity = $this->getMockBuilder(DataObject::class) + ->disableOriginalConstructor() + ->getMock(); + $this->update->execute($entity); + } +} diff --git a/lib/internal/Magento/Framework/Exception/AlreadyExistsException.php b/lib/internal/Magento/Framework/Exception/AlreadyExistsException.php index dd39ec8c709a75fa4c4a8721dd8fdbfed4499c2f..18afdacf53de2676525f70fd76e5b94b3671dddb 100644 --- a/lib/internal/Magento/Framework/Exception/AlreadyExistsException.php +++ b/lib/internal/Magento/Framework/Exception/AlreadyExistsException.php @@ -3,9 +3,24 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Framework\Exception; +use Magento\Framework\Phrase; + +/** + * Class AlreadyExistsException + */ class AlreadyExistsException extends LocalizedException { + /** + * @param Phrase $phrase + * @param \Exception $cause + */ + public function __construct(Phrase $phrase = null, \Exception $cause = null) + { + if ($phrase === null) { + $phrase = new Phrase('Unique constraint violation found'); + } + parent::__construct($phrase, $cause); + } } diff --git a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php index 5687771a80266e636808e92e257985ea27ca91da..812b47ddba854a4988c3e6babad70a501a35cce5 100644 --- a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php +++ b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php @@ -76,6 +76,11 @@ class PluginList extends Scoped implements InterceptionPluginList */ protected $_pluginInstances = []; + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + /** * @param ReaderInterface $reader * @param ScopeInterface $configScope @@ -150,6 +155,7 @@ class PluginList extends Scoped implements InterceptionPluginList } $this->_inherited[$type] = null; if (is_array($plugins) && count($plugins)) { + $this->filterPlugins($plugins); uasort($plugins, [$this, '_sort']); $this->trimInstanceStartingBackslash($plugins); $this->_inherited[$type] = $plugins; @@ -352,4 +358,34 @@ class PluginList extends Scoped implements InterceptionPluginList } } } + + /** + * Remove from list not existing plugins + * + * @param array $plugins + * @return void + */ + private function filterPlugins(array &$plugins) + { + foreach ($plugins as $name => $plugin) { + if (empty($plugin['instance'])) { + unset($plugins[$name]); + $this->getLogger()->info("Reference to undeclared plugin with name '{$name}'."); + } + } + } + + /** + * Returns logger instance + * + * @deprecated + * @return \Psr\Log\LoggerInterface + */ + private function getLogger() + { + if ($this->logger === null) { + $this->logger = $this->_objectManager->get(\Psr\Log\LoggerInterface::class); + } + return $this->logger; + } } diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php index 078a709d94fa2b93fc3eea7f2d1e865b28de8692..f1dc8aee39e967878fc5b4712565f6a03b889e22 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php @@ -40,6 +40,11 @@ class PluginListTest extends \PHPUnit_Framework_TestCase /** @var JsonInterface|\PHPUnit_Framework_MockObject_MockObject */ private $jsonMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $objectManagerMock; + protected function setUp() { $readerMap = include __DIR__ . '/../_files/reader_mock_map.php'; @@ -59,8 +64,8 @@ class PluginListTest extends \PHPUnit_Framework_TestCase $omConfigMock->expects($this->any())->method('getOriginalInstanceType')->will($this->returnArgument(0)); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); + $this->objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); + $this->objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); $definitions = new \Magento\Framework\ObjectManager\Definition\Runtime(); @@ -74,7 +79,7 @@ class PluginListTest extends \PHPUnit_Framework_TestCase 'relations' => new \Magento\Framework\ObjectManager\Relations\Runtime(), 'omConfig' => $omConfigMock, 'definitions' => new \Magento\Framework\Interception\Definition\Runtime(), - 'objectManager' => $objectManagerMock, + 'objectManager' => $this->objectManagerMock, 'classDefinitions' => $definitions, 'scopePriorityScheme' => ['global'], 'cacheId' => 'interception' @@ -242,6 +247,27 @@ class PluginListTest extends \PHPUnit_Framework_TestCase $this->assertEquals(null, $this->object->getNext('Type', 'method')); } + /** + * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext + * @covers \Magento\Framework\Interception\PluginList\PluginList::_inheritPlugins + */ + public function testInheritPluginsWithNotExistingPlugin() + { + $loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $this->objectManagerMock->expects($this->once()) + ->method('get') + ->with(\Psr\Log\LoggerInterface::class) + ->willReturn($loggerMock); + $loggerMock->expects($this->once()) + ->method('info') + ->with("Reference to undeclared plugin with name 'simple_plugin'."); + $this->configScopeMock->expects($this->any()) + ->method('getCurrentScope') + ->will($this->returnValue('frontend')); + + $this->assertNull($this->object->getNext('typeWithoutInstance', 'someMethod')); + } + /** * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext * @covers \Magento\Framework\Interception\PluginList\PluginList::_loadScopedData diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php index 832a5a67599da7f9dbfe46e2732324b536f267c8..87bbe0d35dd2561799cd5d2024347c8fb48c2b73 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php @@ -70,6 +70,11 @@ return [ 'instance' => 'NonExistingPluginClass', ], ], + ], + 'typeWithoutInstance' => [ + 'plugins' => [ + 'simple_plugin' => [], + ], ] ] ] diff --git a/lib/internal/Magento/Framework/Model/AbstractModel.php b/lib/internal/Magento/Framework/Model/AbstractModel.php index dbbb20021b9f4dcc03765aee89bdbd81339bac08..34536d47097796d18e1ff517f2fd1ad2a7317735 100644 --- a/lib/internal/Magento/Framework/Model/AbstractModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractModel.php @@ -527,12 +527,7 @@ abstract class AbstractModel extends \Magento\Framework\DataObject */ public function load($modelId, $field = null) { - $this->_beforeLoad($modelId, $field); $this->_getResource()->load($this, $modelId, $field); - $this->_afterLoad(); - $this->setOrigData(); - $this->_hasDataChanges = false; - $this->updateStoredData(); return $this; } @@ -577,6 +572,18 @@ abstract class AbstractModel extends \Magento\Framework\DataObject return $this; } + /** + * Process operation before object load + * + * @param string $identifier + * @param string|null $field + * @return void + */ + public function beforeLoad($identifier, $field = null) + { + $this->_beforeLoad($identifier, $field); + } + /** * Object after load processing. Implemented as public interface for supporting objects after load in collections * @@ -584,7 +591,6 @@ abstract class AbstractModel extends \Magento\Framework\DataObject */ public function afterLoad() { - $this->getResource()->afterLoad($this); $this->_afterLoad(); $this->updateStoredData(); return $this; diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index fc189473505abd258acb159f32ff9d2b77d44958..4cd88e356e9cad79a6f0209bb2bab685ffe5f73c 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -10,6 +10,8 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\ResourceModel\AbstractResource; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\Phrase; /** * Abstract resource model class @@ -332,6 +334,7 @@ abstract class AbstractDb extends AbstractResource */ public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null) { + $object->beforeLoad($value, $field); if ($field === null) { $field = $this->getIdFieldName(); } @@ -348,7 +351,10 @@ abstract class AbstractDb extends AbstractResource $this->unserializeFields($object); $this->_afterLoad($object); - + $object->afterLoad(); + $object->setOrigData(); + $object->setHasDataChanges(false); + return $this; } @@ -375,6 +381,7 @@ abstract class AbstractDb extends AbstractResource * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @throws \Exception + * @throws AlreadyExistsException * @api */ public function save(\Magento\Framework\Model\AbstractModel $object) @@ -409,6 +416,10 @@ abstract class AbstractDb extends AbstractResource } $this->addCommitCallback([$object, 'afterCommitCallback'])->commit(); $object->setHasDataChanges(false); + } catch (DuplicateException $e) { + $this->rollBack(); + $object->setHasDataChanges(true); + throw new AlreadyExistsException(new Phrase('Unique constraint violation found'), $e); } catch (\Exception $e) { $this->rollBack(); $object->setHasDataChanges(true); diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php index 1dcb84d276840185af916c1bc6036cc4b1ea5071..d13bcdc539aa8c82d7af3b24ea1d32ed93e8431b 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php @@ -7,6 +7,10 @@ // @codingStandardsIgnoreFile namespace Magento\Framework\Model\Test\Unit\ResourceModel\Db; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Adapter\DuplicateException; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -14,7 +18,7 @@ namespace Magento\Framework\Model\Test\Unit\ResourceModel\Db; class AbstractDbTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @var AbstractDb */ protected $_model; @@ -63,7 +67,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase ->willReturn($this->transactionManagerMock); $this->_model = $this->getMockForAbstractClass( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [$contextMock], '', true, @@ -116,7 +120,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testAddUniqueFieldArray() { $this->assertInstanceOf( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, $this->_model->addUniqueField(['someField']) ); } @@ -134,7 +138,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase { $data = 'MainTableName'; $idFieldNameProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, '_idFieldName' + AbstractDb::class, '_idFieldName' ); $idFieldNameProperty->setAccessible(true); $idFieldNameProperty->setValue($this->_model, $data); @@ -158,7 +162,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testGetMainTable($tableName, $expectedResult) { $mainTableProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableProperty->setAccessible(true); @@ -195,7 +199,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $this->returnValue('tableName') ); $tablesProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_tables' ); $tablesProperty->setAccessible(true); @@ -215,7 +219,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase */ public function testGetChecksum($checksum, $expected) { - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class, [], [], '', false); + $connectionMock = $this->getMock(AdapterInterface::class, [], [], '', false); $connectionMock->expects($this->once())->method('getTablesChecksum')->with($checksum)->will( $this->returnValue([$checksum => 'checksum']) ); @@ -242,7 +246,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testResetUniqueField() { $uniqueFields = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_uniqueFields' ); $uniqueFields->setAccessible(true); @@ -254,7 +258,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testGetUniqueFields() { $uniqueFieldsReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_uniqueFields' ); $uniqueFieldsReflection->setAccessible(true); @@ -269,36 +273,26 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testLoad() { - $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); - $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); - $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, - [$contextMock, $registryMock], - '', - false, - true, - true, - ['__wakeup'] - ); - - $value = 'some_value'; - $idFieldName = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - '_idFieldName' - ); - $idFieldName->setAccessible(true); - $idFieldName->setValue($this->_model, 'field_value'); - + /** @var \Magento\Framework\Model\AbstractModel|\PHPUnit_Framework_MockObject_MockObject $object */ + $object = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) + ->disableOriginalConstructor() + ->getMock(); + $object->expects($this->once())->method('beforeLoad')->with('some_value', 'field_name'); + $object->expects($this->once())->method('afterLoad')->willReturnSelf(); + $object->expects($this->once())->method('setOrigData')->willReturnSelf(); + $object->expects($this->once())->method('setHasDataChanges')->with(false)->willReturnSelf(); + $result = $this->_model->load($object, 'some_value', 'field_name'); + $this->assertEquals($this->_model, $result); $this->assertInstanceOf( \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, - $this->_model->load($abstractModelMock, $value, $idFieldName) + $result ); } public function testDelete() { $connectionInterfaceMock = $this->getMock( - \Magento\Framework\DB\Adapter\AdapterInterface::class, + AdapterInterface::class, [], [], '', @@ -307,7 +301,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$contextMock, $registryMock], '', false, @@ -321,7 +315,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase ); $abstractModelMock->expects($this->once())->method('getData')->willReturn(['data' => 'value']); - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $connectionMock = $this->getMock(AdapterInterface::class); $this->transactionManagerMock->expects($this->once()) ->method('start') ->with($connectionInterfaceMock) @@ -344,13 +338,13 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $this->returnValue('tableName') ); $mainTableReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableReflection->setAccessible(true); $mainTableReflection->setValue($this->_model, 'tableName'); $idFieldNameReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_idFieldName' ); $idFieldNameReflection->setAccessible(true); @@ -361,7 +355,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $abstractModelMock->expects($this->once())->method('afterDelete'); $abstractModelMock->expects($this->once())->method('afterDeleteCommit'); $this->assertInstanceOf( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, $this->_model->delete($abstractModelMock) ); } @@ -371,7 +365,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$contextMock, $registryMock], '', false, @@ -391,7 +385,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testGetDataChanged($getOriginData, $expected) { $connectionInterfaceMock = $this->getMock( - \Magento\Framework\DB\Adapter\AdapterInterface::class, + AdapterInterface::class, [], [], '', @@ -403,7 +397,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $contextMock = $this->getMock(\Magento\Framework\Model\Context::class, [], [], '', false); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$contextMock, $registryMock], '', false, @@ -412,7 +406,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase ['__wakeup', 'getOrigData', 'getData'] ); $mainTableProperty = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableProperty->setAccessible(true); @@ -441,13 +435,13 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase public function testPrepareDataForUpdate() { - $connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class, [], [], '', false); + $connectionMock = $this->getMock(AdapterInterface::class, [], [], '', false); $context = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( \Magento\Framework\Model\Context::class ); $registryMock = $this->getMock(\Magento\Framework\Registry::class, [], [], '', false); $resourceMock = $this->getMock( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, [ '_construct', 'getConnection', @@ -459,7 +453,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase false ); $connectionInterfaceMock = $this->getMock( - \Magento\Framework\DB\Adapter\AdapterInterface::class, + AdapterInterface::class, [], [], '', @@ -472,7 +466,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMockForAbstractClass(); $abstractModelMock = $this->getMockForAbstractClass( - \Magento\Framework\Model\AbstractModel::class, + AbstractModel::class, [$context, $registryMock, $resourceMock, $resourceCollectionMock] ); $data = 'tableName'; @@ -484,20 +478,19 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $this->returnValue('tableName') ); $mainTableReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_mainTable' ); $mainTableReflection->setAccessible(true); $mainTableReflection->setValue($this->_model, 'tableName'); $idFieldNameReflection = new \ReflectionProperty( - \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, + AbstractDb::class, '_idFieldName' ); $idFieldNameReflection->setAccessible(true); $idFieldNameReflection->setValue($this->_model, 'idFieldName'); $connectionMock->expects($this->any())->method('save')->with('tableName', 'idFieldName'); $connectionMock->expects($this->any())->method('quoteInto')->will($this->returnValue('idFieldName')); - $abstractModelMock->setIdFieldName('id'); $abstractModelMock->setData( [ @@ -551,7 +544,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase /** * Mock SUT so as not to test extraneous logic */ - $model = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class) + $model = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->setMethods(['_prepareDataForSave', 'getIdFieldName', 'getConnection', 'getMainTable']) ->getMockForAbstractClass(); @@ -568,7 +561,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $reflectionProperty->setValue($model, $pkIncrement); // Mocked behavior - $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + $connectionMock = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() ->setMethods(['lastInsertId']) ->getMockForAbstractClass(); @@ -590,7 +583,7 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase // Only set object id if not PK autoincrement $setIdInvokedCount = $pkIncrement ? 1 : 0; - $inputObject = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) + $inputObject = $this->getMockBuilder(AbstractModel::class) ->disableOriginalConstructor() ->getMock(); $inputObject->expects($this->exactly($setIdInvokedCount))->method('setId'); @@ -602,9 +595,37 @@ class AbstractDbTest extends \PHPUnit_Framework_TestCase $reflectionMethod->invokeArgs($model, [$inputObject]); } + /** + * @return array + */ public function saveNewObjectDataProvider() { return [[true], [false]]; } + /** + * @expectedException \Magento\Framework\Exception\AlreadyExistsException + */ + public function testDuplicateExceptionProcessingOnSave() + { + $connection = $this->getMock(AdapterInterface::class); + $connection->expects($this->once())->method('rollback'); + + /** @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject $model */ + $model = $this->getMockBuilder(AbstractDb::class) + ->disableOriginalConstructor() + ->setMethods(['getConnection']) + ->getMockForAbstractClass(); + $model->expects($this->any())->method('getConnection')->willReturn($connection); + + /** @var AbstractModel|\PHPUnit_Framework_MockObject_MockObject $object */ + $object = $this->getMockBuilder(AbstractModel::class) + ->disableOriginalConstructor() + ->getMock(); + $object->expects($this->once())->method('hasDataChanges')->willReturn(true); + $object->expects($this->once())->method('beforeSave')->willThrowException(new DuplicateException()); + $object->expects($this->once())->method('setHasDataChanges')->with(true); + + $model->save($object); + } } diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php index cd7a236f723590ffe5138fa063dabfa5968c8c3b..2cbf5da0bfbbba3ab562942c068913f5d0f12069 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php @@ -40,13 +40,13 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->connectionMock = $this->getMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, [], [], '', false); - $this->resourceMock = $this->getMock( - \Magento\Framework\App\ResourceConnection::class, - [], - [], - '', - false, - false + $this->resourceMock = $this->getMock( + \Magento\Framework\App\ResourceConnection::class, + [], + [], + '', + false, + false ); $this->connectionMock->expects($this->any()) @@ -57,19 +57,19 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase ->method('getConnection') ->willReturn($this->connectionMock); - $this->triggerFactoryMock = $this->getMock( + $this->triggerFactoryMock = $this->getMock( \Magento\Framework\DB\Ddl\TriggerFactory::class, [], [], '', false, false ); - $this->viewCollectionMock = $this->getMockForAbstractClass( + $this->viewCollectionMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\CollectionInterface::class, [], '', false, false, true, [] ); - $this->viewMock = $this->getMockForAbstractClass( + $this->viewMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, [] ); $this->resourceMock->expects($this->any()) ->method('getTableName') - ->willReturn($this->tableName); + ->will($this->returnArgument(0)); $this->model = new Subscription( $this->resourceMock, @@ -96,11 +96,15 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase $this->assertEquals('columnName', $this->model->getColumnName()); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testCreate() { $triggerName = 'trigger_name'; $this->resourceMock->expects($this->atLeastOnce())->method('getTriggerName')->willReturn($triggerName); $triggerMock = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Trigger::class) + ->setMethods(['setName', 'getName', 'setTime', 'setEvent', 'setTable', 'addStatement']) ->disableOriginalConstructor() ->getMock(); $triggerMock->expects($this->exactly(3)) @@ -121,11 +125,38 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase ->method('setTable') ->with($this->tableName) ->will($this->returnSelf()); - $triggerMock->expects($this->exactly(6)) + + $triggerMock->expects($this->at(4)) + ->method('addStatement') + ->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(5)) + ->method('addStatement') + ->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(11)) + ->method('addStatement') + ->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(12)) + ->method('addStatement') + ->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (NEW.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(18)) + ->method('addStatement') + ->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (OLD.columnName);") + ->will($this->returnSelf()); + + $triggerMock->expects($this->at(19)) ->method('addStatement') + ->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (OLD.columnName);") ->will($this->returnSelf()); - $changelogMock = $this->getMockForAbstractClass( + $changelogMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\ChangelogInterface::class, [], '', false, false, true, [] ); $changelogMock->expects($this->exactly(3)) @@ -143,7 +174,7 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase ->method('create') ->will($this->returnValue($triggerMock)); - $otherChangelogMock = $this->getMockForAbstractClass( + $otherChangelogMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\ChangelogInterface::class, [], '', false, false, true, [] ); $otherChangelogMock->expects($this->exactly(3)) @@ -153,7 +184,7 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase ->method('getColumnName') ->will($this->returnValue('entity_id')); - $otherViewMock = $this->getMockForAbstractClass( + $otherViewMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, [] ); $otherViewMock->expects($this->exactly(1)) @@ -216,7 +247,7 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase ->method('create') ->will($this->returnValue($triggerMock)); - $otherChangelogMock = $this->getMockForAbstractClass( + $otherChangelogMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\View\ChangelogInterface::class, [], '', false, false, true, [] ); $otherChangelogMock->expects($this->exactly(3)) @@ -226,7 +257,7 @@ class SubscriptionTest extends \PHPUnit_Framework_TestCase ->method('getColumnName') ->will($this->returnValue('entity_id')); - $otherViewMock = $this->getMockForAbstractClass( + $otherViewMock = $this->getMockForAbstractClass( \Magento\Framework\Mview\ViewInterface::class, [], '', false, false, true, [] ); $otherViewMock->expects($this->exactly(1)) diff --git a/lib/internal/Magento/Framework/Mview/etc/mview.xsd b/lib/internal/Magento/Framework/Mview/etc/mview.xsd index d171699c3784a8437985b6d741b0bc5d8297373a..0521691e852368d1e157733ff31e71c3ea41a07f 100644 --- a/lib/internal/Magento/Framework/Mview/etc/mview.xsd +++ b/lib/internal/Magento/Framework/Mview/etc/mview.xsd @@ -106,7 +106,7 @@ <xs:simpleType name="subscriptionModelType"> <xs:annotation> <xs:documentation> - Subscription model must be a valid PHP class or interface name. + DEPRECATED. Subscription model must be a valid PHP class or interface name. </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> diff --git a/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php b/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php index 4048f2d105ac0f1d455e2a07f9ecc4acdc9bd962..ddf14cdc659a26533a60f4e79a25b39e5b94fddd 100644 --- a/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php +++ b/lib/internal/Magento/Framework/Pricing/Render/PriceBox.php @@ -86,7 +86,7 @@ class PriceBox extends Template implements PriceBoxRenderInterface, IdentityInte */ protected function getCacheLifetime() { - return parent::hasCacheLifetime() ? parent::getCacheLifetime() : self::DEFAULT_LIFETIME; + return parent::hasCacheLifetime() ? parent::getCacheLifetime() : null; } /** diff --git a/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php b/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php index 86e2dde6359d8a9f759905042d86521c135da04d..9a0dfcd7099ee94718f38f18c4af3ab3ed521109 100644 --- a/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php +++ b/lib/internal/Magento/Framework/Pricing/Test/Unit/Render/PriceBoxTest.php @@ -241,4 +241,17 @@ class PriceBoxTest extends \PHPUnit_Framework_TestCase { $this->assertEquals($this->rendererPool, $this->model->getRendererPool()); } + + /** + * This tests ensures that protected method getCacheLifetime() returns a null value when cacheLifeTime is not + * explicitly set in the parent block + */ + public function testCacheLifetime() + { + $reflectionClass = new \ReflectionClass(get_class($this->model)); + $methodReflection = $reflectionClass->getMethod('getCacheLifetime'); + $methodReflection->setAccessible(true); + $cacheLifeTime = $methodReflection->invoke($this->model); + $this->assertNull($cacheLifeTime, 'Expected null cache lifetime'); + } } diff --git a/lib/internal/Magento/Framework/View/Config.php b/lib/internal/Magento/Framework/View/Config.php index 917f632323ea0cda55ab95b55cd663292b5b31d6..b72fe87c4acc4b7155437bc8b478571caa4e72b2 100644 --- a/lib/internal/Magento/Framework/View/Config.php +++ b/lib/internal/Magento/Framework/View/Config.php @@ -62,7 +62,7 @@ class Config implements \Magento\Framework\View\ConfigInterface if (isset($params['themeModel'])) { /** @var \Magento\Framework\View\Design\ThemeInterface $currentTheme */ $currentTheme = $params['themeModel']; - $key = $currentTheme->getCode(); + $key = $currentTheme->getFullPath(); if (isset($this->viewConfigs[$key])) { return $this->viewConfigs[$key]; } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/ConfigTest.php index 4295b5d3e24e73810f66e1a16e83f1d0cb6e84f1..2a09783c710e4e4c9334079a67025b452d673974 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/ConfigTest.php @@ -40,17 +40,17 @@ class ConfigTest extends \PHPUnit_Framework_TestCase public function testGetViewConfig() { - $themeCode = 2; + $themeCode = 'area/theme'; $themeMock = $this->getMock( \Magento\Theme\Model\Theme::class, - ['getCode'], + ['getFullPath'], [], '', false ); $themeMock->expects($this->atLeastOnce()) - ->method('getCode') + ->method('getFullPath') ->will($this->returnValue($themeCode)); $params = [ 'themeModel' => $themeMock, diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js index a74a9f12fb2dfe1a0ea5e79b445608016c61462c..f224218f4f731511b8ba13f3ded06b5a73393f4d 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js @@ -46,7 +46,7 @@ define([ }; schema.flowContent = schema.blockContent.concat(schema.phrasingContent, ['style']); - schema.nonEmpty = ['td', 'th', 'iframe', 'video', 'audio', 'object', 'script'].concat(schema.shortEnded); + schema.nonEmpty = ['td', 'th', 'iframe', 'video', 'audio', 'object', 'script', 'i', 'em', 'span'].concat(schema.shortEnded); _.extend(schema, (function (phrasingContent, flowContent) { var validElements = [], diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js index 4964e6bb14e703f3a0a11a84ab1af78c7e9cfe6e..167903f62c760ca8ce495efe0dc42c98546d161f 100755 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -101,6 +101,8 @@ define([ magentoPluginsOptions: magentoPluginsOptions, doctype: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">', setup: function(ed){ + ed.onPreInit.add(self.onEditorPreInit.bind(self)); + ed.onInit.add(self.onEditorInit.bind(self)); ed.onSubmit.add(function(ed, e) { @@ -285,10 +287,18 @@ define([ } }, - onEditorInit: function (editor) { + /** + * Editor pre-initialise event handler. + */ + onEditorPreInit: function (editor) { this.applySchema(editor); }, + /** + * @deprecated + */ + onEditorInit: function () {}, + onFormValidation: function() { if (tinyMCE.get(this.id)) { $(this.id).value = tinyMCE.get(this.id).getContent(); @@ -347,6 +357,8 @@ define([ // escape special chars in directives url to use it in regular expression var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'); var reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)')); + content = decodeURIComponent(content); + return content.gsub(reg, function(match) { return Base64.mageDecode(match[1]); }.bind(this)); diff --git a/lib/web/mage/dataPost.js b/lib/web/mage/dataPost.js index e1c297ffcef4b9a6a3d64806d5f93eb57e938644..2d47929f57213c338c5b121310c544b3876331ec 100644 --- a/lib/web/mage/dataPost.js +++ b/lib/web/mage/dataPost.js @@ -2,50 +2,88 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ -/*jshint browser:true jquery:true*/ -/*global confirm:true*/ + define([ - "jquery", - "mage/template", - "jquery/ui" -], function($,mageTemplate){ - + 'jquery', + 'mage/template', + 'Magento_Ui/js/modal/confirm', + 'jquery/ui' +], function ($, mageTemplate, uiConfirm) { + 'use strict'; + $.widget('mage.dataPost', { options: { - formTemplate: '<form action="<%- data.action %>" method="post">' - + '<% _.each(data.data, function(value, index) { %>' - + '<input name="<%- index %>" value="<%- value %>">' - + '<% }) %></form>', + formTemplate: '<form action="<%- data.action %>" method="post">' + + '<% _.each(data.data, function(value, index) { %>' + + '<input name="<%- index %>" value="<%- value %>">' + + '<% }) %></form>', postTrigger: ['a[data-post]', 'button[data-post]', 'span[data-post]'], formKeyInputSelector: 'input[name="form_key"]' }, - _create: function() { + + /** @inheritdoc */ + _create: function () { this._bind(); }, - _bind: function() { + + /** @inheritdoc */ + _bind: function () { var events = {}; - $.each(this.options.postTrigger, function(index, value) { + + $.each(this.options.postTrigger, function (index, value) { events['click ' + value] = '_postDataAction'; }); + this._on(events); }, - _postDataAction: function(e) { - e.preventDefault(); + + /** + * Handler for click. + * + * @param {Object} e + * @private + */ + _postDataAction: function (e) { var params = $(e.currentTarget).data('post'); + + e.preventDefault(); this.postData(params); }, - postData: function(params) { - var formKey = $(this.options.formKeyInputSelector).val(); + + /** + * Data post action. + * + * @param {Object} params + */ + postData: function (params) { + var formKey = $(this.options.formKeyInputSelector).val(), + $form; + if (formKey) { - params.data.form_key = formKey; + params.data['form_key'] = formKey; } - $(mageTemplate(this.options.formTemplate, { + + $form = $(mageTemplate(this.options.formTemplate, { data: params - })).appendTo('body').hide().submit(); + })); + + if (params.data.confirmation) { + uiConfirm({ + content: params.data.confirmationMessage, + actions: { + /** @inheritdoc */ + confirm: function () { + $form.appendTo('body').hide().submit(); + } + } + }); + } else { + $form.appendTo('body').hide().submit(); + } } }); - + $(document).dataPost(); return $.mage.dataPost; -}); \ No newline at end of file +}); diff --git a/lib/web/mage/utils/misc.js b/lib/web/mage/utils/misc.js index b3bc52b7164f5512057b624cdaff96a2cfca311a..c6440b8929395419053ab53961f5fcc231ab7eee 100644 --- a/lib/web/mage/utils/misc.js +++ b/lib/web/mage/utils/misc.js @@ -39,6 +39,7 @@ define([ 'EEE': 'ddd', 'e': 'd', 'yyyy': 'YYYY', + 'yy': 'YY', 'y': 'YYYY', 'a': 'A' }; diff --git a/lib/web/moment-timezone-with-data.js b/lib/web/moment-timezone-with-data.js new file mode 100644 index 0000000000000000000000000000000000000000..6dfd9559f93d4c731872460f2ccc5009eca96130 --- /dev/null +++ b/lib/web/moment-timezone-with-data.js @@ -0,0 +1,7 @@ +//! moment-timezone.js +//! version : 0.5.5 +//! author : Tim Wood +//! license : MIT +//! github.com/moment/moment-timezone +!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["moment"],b):"object"==typeof module&&module.exports?module.exports=b(require("moment")):b(a.moment)}(this,function(a){"use strict";function b(a){return a>96?a-87:a>64?a-29:a-48}function c(a){var c,d=0,e=a.split("."),f=e[0],g=e[1]||"",h=1,i=0,j=1;for(45===a.charCodeAt(0)&&(d=1,j=-1),d;d<f.length;d++)c=b(f.charCodeAt(d)),i=60*i+c;for(d=0;d<g.length;d++)h/=60,c=b(g.charCodeAt(d)),i+=c*h;return i*j}function d(a){for(var b=0;b<a.length;b++)a[b]=c(a[b])}function e(a,b){for(var c=0;c<b;c++)a[c]=Math.round((a[c-1]||0)+6e4*a[c]);a[b-1]=1/0}function f(a,b){var c,d=[];for(c=0;c<b.length;c++)d[c]=a[b[c]];return d}function g(a){var b=a.split("|"),c=b[2].split(" "),g=b[3].split(""),h=b[4].split(" ");return d(c),d(g),d(h),e(h,g.length),{name:b[0],abbrs:f(b[1].split(" "),g),offsets:f(c,g),untils:h,population:0|b[5]}}function h(a){a&&this._set(g(a))}function i(a){var b=a.toTimeString(),c=b.match(/\([a-z ]+\)/i);c&&c[0]?(c=c[0].match(/[A-Z]/g),c=c?c.join(""):void 0):(c=b.match(/[A-Z]{3,5}/g),c=c?c[0]:void 0),"GMT"===c&&(c=void 0),this.at=+a,this.abbr=c,this.offset=a.getTimezoneOffset()}function j(a){this.zone=a,this.offsetScore=0,this.abbrScore=0}function k(a,b){for(var c,d;d=6e4*((b.at-a.at)/12e4|0);)c=new i(new Date(a.at+d)),c.offset===a.offset?a=c:b=c;return a}function l(){var a,b,c,d=(new Date).getFullYear()-2,e=new i(new Date(d,0,1)),f=[e];for(c=1;c<48;c++)b=new i(new Date(d,c,1)),b.offset!==e.offset&&(a=k(e,b),f.push(a),f.push(new i(new Date(a.at+6e4)))),e=b;for(c=0;c<4;c++)f.push(new i(new Date(d+c,0,1))),f.push(new i(new Date(d+c,6,1)));return f}function m(a,b){return a.offsetScore!==b.offsetScore?a.offsetScore-b.offsetScore:a.abbrScore!==b.abbrScore?a.abbrScore-b.abbrScore:b.zone.population-a.zone.population}function n(a,b){var c,e;for(d(b),c=0;c<b.length;c++)e=b[c],I[e]=I[e]||{},I[e][a]=!0}function o(a){var b,c,d,e=a.length,f={},g=[];for(b=0;b<e;b++){d=I[a[b].offset]||{};for(c in d)d.hasOwnProperty(c)&&(f[c]=!0)}for(b in f)f.hasOwnProperty(b)&&g.push(H[b]);return g}function p(){try{var a=Intl.DateTimeFormat().resolvedOptions().timeZone;if(a){var b=H[r(a)];if(b)return b;z("Moment Timezone found "+a+" from the Intl api, but did not have that data loaded.")}}catch(c){}var d,e,f,g=l(),h=g.length,i=o(g),k=[];for(e=0;e<i.length;e++){for(d=new j(t(i[e]),h),f=0;f<h;f++)d.scoreOffsetAt(g[f]);k.push(d)}return k.sort(m),k.length>0?k[0].zone.name:void 0}function q(a){return D&&!a||(D=p()),D}function r(a){return(a||"").toLowerCase().replace(/\//g,"_")}function s(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)d=a[b].split("|"),c=d[0],e=r(c),F[e]=a[b],H[e]=c,d[5]&&n(e,d[2].split(" "))}function t(a,b){a=r(a);var c,d=F[a];return d instanceof h?d:"string"==typeof d?(d=new h(d),F[a]=d,d):G[a]&&b!==t&&(c=t(G[a],t))?(d=F[a]=new h,d._set(c),d.name=H[a],d):null}function u(){var a,b=[];for(a in H)H.hasOwnProperty(a)&&(F[a]||F[G[a]])&&H[a]&&b.push(H[a]);return b.sort()}function v(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)c=a[b].split("|"),d=r(c[0]),e=r(c[1]),G[d]=e,H[d]=c[0],G[e]=d,H[e]=c[1]}function w(a){s(a.zones),v(a.links),A.dataVersion=a.version}function x(a){return x.didShowError||(x.didShowError=!0,z("moment.tz.zoneExists('"+a+"') has been deprecated in favor of !moment.tz.zone('"+a+"')")),!!t(a)}function y(a){return!(!a._a||void 0!==a._tzm)}function z(a){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(a)}function A(b){var c=Array.prototype.slice.call(arguments,0,-1),d=arguments[arguments.length-1],e=t(d),f=a.utc.apply(null,c);return e&&!a.isMoment(b)&&y(f)&&f.add(e.parse(f),"minutes"),f.tz(d),f}function B(a){return function(){return this._z?this._z.abbr(this):a.call(this)}}function C(a){return function(){return this._z=null,a.apply(this,arguments)}}if(void 0!==a.tz)return z("Moment Timezone "+a.tz.version+" was already loaded "+(a.tz.dataVersion?"with data from ":"without any data")+a.tz.dataVersion),a;var D,E="0.5.5",F={},G={},H={},I={},J=a.version.split("."),K=+J[0],L=+J[1];(K<2||2===K&&L<6)&&z("Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js "+a.version+". See momentjs.com"),h.prototype={_set:function(a){this.name=a.name,this.abbrs=a.abbrs,this.untils=a.untils,this.offsets=a.offsets,this.population=a.population},_index:function(a){var b,c=+a,d=this.untils;for(b=0;b<d.length;b++)if(c<d[b])return b},parse:function(a){var b,c,d,e,f=+a,g=this.offsets,h=this.untils,i=h.length-1;for(e=0;e<i;e++)if(b=g[e],c=g[e+1],d=g[e?e-1:e],b<c&&A.moveAmbiguousForward?b=c:b>d&&A.moveInvalidForward&&(b=d),f<h[e]-6e4*b)return g[e];return g[i]},abbr:function(a){return this.abbrs[this._index(a)]},offset:function(a){return this.offsets[this._index(a)]}},j.prototype.scoreOffsetAt=function(a){this.offsetScore+=Math.abs(this.zone.offset(a.at)-a.offset),this.zone.abbr(a.at).replace(/[^A-Z]/g,"")!==a.abbr&&this.abbrScore++},A.version=E,A.dataVersion="",A._zones=F,A._links=G,A._names=H,A.add=s,A.link=v,A.load=w,A.zone=t,A.zoneExists=x,A.guess=q,A.names=u,A.Zone=h,A.unpack=g,A.unpackBase60=c,A.needsOffset=y,A.moveInvalidForward=!0,A.moveAmbiguousForward=!1;var M=a.fn;a.tz=A,a.defaultZone=null,a.updateOffset=function(b,c){var d,e=a.defaultZone;void 0===b._z&&(e&&y(b)&&!b._isUTC&&(b._d=a.utc(b._a)._d,b.utc().add(e.parse(b),"minutes")),b._z=e),b._z&&(d=b._z.offset(b),Math.abs(d)<16&&(d/=60),void 0!==b.utcOffset?b.utcOffset(-d,c):b.zone(d,c))},M.tz=function(b){return b?(this._z=t(b),this._z?a.updateOffset(this):z("Moment Timezone has no data for "+b+". See http://momentjs.com/timezone/docs/#/data-loading/."),this):this._z?this._z.name:void 0},M.zoneName=B(M.zoneName),M.zoneAbbr=B(M.zoneAbbr),M.utc=C(M.utc),a.tz.setDefault=function(b){return(K<2||2===K&&L<9)&&z("Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js "+a.version+"."),a.defaultZone=b?t(b):null,a};var N=a.momentProperties;return"[object Array]"===Object.prototype.toString.call(N)?(N.push("_z"),N.push("_a")):N&&(N._z=null),w({version:"2016f",zones:["Africa/Abidjan|LMT GMT|g.8 0|01|-2ldXH.Q|48e5","Africa/Accra|LMT GMT GHST|.Q 0 -k|012121212121212121212121212121212121212121212121|-26BbX.8 6tzX.8 MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE 1BAk MnE 1C0k MnE 1BAk MnE 1BAk MnE|41e5","Africa/Nairobi|LMT EAT BEAT BEAUT|-2r.g -30 -2u -2J|01231|-1F3Cr.g 3Dzr.g okMu MFXJ|47e5","Africa/Algiers|PMT WET WEST CET CEST|-9.l 0 -10 -10 -20|0121212121212121343431312123431213|-2nco9.l cNb9.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 DA0 Imo0 rd0 De0 9Xz0 1fb0 1ap0 16K0 2yo0 mEp0 hwL0 jxA0 11A0 dDd0 17b0 11B0 1cN0 2Dy0 1cN0 1fB0 1cL0|26e5","Africa/Lagos|LMT WAT|-d.A -10|01|-22y0d.A|17e6","Africa/Bissau|LMT WAT GMT|12.k 10 0|012|-2ldWV.E 2xonV.E|39e4","Africa/Maputo|LMT CAT|-2a.k -20|01|-2GJea.k|26e5","Africa/Cairo|EET EEST|-20 -30|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-1bIO0 vb0 1ip0 11z0 1iN0 1nz0 12p0 1pz0 10N0 1pz0 16p0 1jz0 s3d0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1WL0 rd0 1Rz0 wp0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1qL0 Xd0 1oL0 11d0 1oL0 11d0 1pb0 11d0 1oL0 11d0 1oL0 11d0 1ny0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 WL0 1qN0 Rb0 1wp0 On0 1zd0 Lz0 1EN0 Fb0 c10 8n0 8Nd0 gL0 e10 mn0|15e6","Africa/Casablanca|LMT WET WEST CET|u.k 0 -10 -10|0121212121212121213121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2gMnt.E 130Lt.E rb0 Dd0 dVb0 b6p0 TX0 EoB0 LL0 gnd0 rz0 43d0 AL0 1Nd0 XX0 1Cp0 pz0 dEp0 4mn0 SyN0 AL0 1Nd0 wn0 1FB0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0 11A0 5A0 e00 17c0 1fA0 1a00 1a00 1fA0 17c0 1io0 14o0 1lc0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1lc0 14o0 1fA0|32e5","Africa/Ceuta|WET WEST CET CEST|0 -10 -10 -20|010101010101010101010232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-25KN0 11z0 drd0 18o0 3I00 17c0 1fA0 1a00 1io0 1a00 1y7p0 LL0 gnd0 rz0 43d0 AL0 1Nd0 XX0 1Cp0 pz0 dEp0 4VB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|85e3","Africa/El_Aaiun|LMT WAT WET WEST|Q.M 10 0 -10|01232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1rDz7.c 1GVA7.c 6L0 AL0 1Nd0 XX0 1Cp0 pz0 1cBB0 AL0 1Nd0 wn0 1FB0 Db0 1zd0 Lz0 1Nf0 wM0 co0 go0 1o00 s00 dA0 vc0 11A0 A00 e00 y00 11A0 uM0 e00 Dc0 11A0 s00 e00 IM0 WM0 mo0 gM0 LA0 WM0 jA0 e00 Rc0 11A0 e00 e00 U00 11A0 8o0 e00 11A0 11A0 5A0 e00 17c0 1fA0 1a00 1a00 1fA0 17c0 1io0 14o0 1lc0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1lc0 14o0 1fA0|20e4","Africa/Johannesburg|SAST SAST SAST|-1u -20 -30|012121|-2GJdu 1Ajdu 1cL0 1cN0 1cL0|84e5","Africa/Khartoum|LMT CAT CAST EAT|-2a.8 -20 -30 -30|01212121212121212121212121212121213|-1yW2a.8 1zK0a.8 16L0 1iN0 17b0 1jd0 17b0 1ip0 17z0 1i10 17X0 1hB0 18n0 1hd0 19b0 1gp0 19z0 1iN0 17b0 1ip0 17z0 1i10 18n0 1hd0 18L0 1gN0 19b0 1gp0 19z0 1iN0 17z0 1i10 17X0 yGd0|51e5","Africa/Monrovia|MMT LRT GMT|H.8 I.u 0|012|-23Lzg.Q 29s01.m|11e5","Africa/Ndjamena|LMT WAT WAST|-10.c -10 -20|0121|-2le10.c 2J3c0.c Wn0|13e5","Africa/Tripoli|LMT CET CEST EET|-Q.I -10 -20 -20|012121213121212121212121213123123|-21JcQ.I 1hnBQ.I vx0 4iP0 xx0 4eN0 Bb0 7ip0 U0n0 A10 1db0 1cN0 1db0 1dd0 1db0 1eN0 1bb0 1e10 1cL0 1c10 1db0 1dd0 1db0 1cN0 1db0 1q10 fAn0 1ep0 1db0 AKq0 TA0 1o00|11e5","Africa/Tunis|PMT CET CEST|-9.l -10 -20|0121212121212121212121212121212121|-2nco9.l 18pa9.l 1qM0 DA0 3Tc0 11B0 1ze0 WM0 7z0 3d0 14L0 1cN0 1f90 1ar0 16J0 1gXB0 WM0 1rA0 11c0 nwo0 Ko0 1cM0 1cM0 1rA0 10M0 zuM0 10N0 1aN0 1qM0 WM0 1qM0 11A0 1o00|20e5","Africa/Windhoek|SWAT SAST SAST CAT WAT WAST|-1u -20 -30 -20 -10 -20|012134545454545454545454545454545454545454545454545454545454545454545454545454545454545454545|-2GJdu 1Ajdu 1cL0 1SqL0 9NA0 11D0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 11B0 1nX0 11B0|32e4","America/Adak|NST NWT NPT BST BDT AHST HST HDT|b0 a0 a0 b0 a0 a0 a0 90|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17SX0 8wW0 iB0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cm0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|326","America/Anchorage|CAT CAWT CAPT AHST AHDT YST AKST AKDT|a0 90 90 a0 90 90 90 80|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17T00 8wX0 iA0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cm0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|30e4","America/Port_of_Spain|LMT AST|46.4 40|01|-2kNvR.U|43e3","America/Araguaina|LMT BRT BRST|3c.M 30 20|0121212121212121212121212121212121212121212121212121|-2glwL.c HdKL.c 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 dMN0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 ny10 Lz0|14e4","America/Argentina/Buenos_Aires|CMT ART ARST ART ARST|4g.M 40 30 30 20|0121212121212121212121212121212121212121213434343434343234343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 g0p0 10M0 j3c0 uL0 1qN0 WL0","America/Argentina/Catamarca|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343454343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 ako0 7B0 8zb0 uL0","America/Argentina/Cordoba|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343454343234343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 j3c0 uL0 1qN0 WL0","America/Argentina/Jujuy|CMT ART ARST ART ARST WART WARST|4g.M 40 30 30 20 40 30|01212121212121212121212121212121212121212134343456543432343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1ze0 TX0 1ld0 WK0 1wp0 TX0 g0p0 10M0 j3c0 uL0","America/Argentina/La_Rioja|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|01212121212121212121212121212121212121212134343434534343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Qn0 qO0 16n0 Rb0 1wp0 TX0 g0p0 10M0 ako0 7B0 8zb0 uL0","America/Argentina/Mendoza|CMT ART ARST ART ARST WART WARST|4g.M 40 30 30 20 40 30|0121212121212121212121212121212121212121213434345656543235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1u20 SL0 1vd0 Tb0 1wp0 TW0 g0p0 10M0 agM0 Op0 7TX0 uL0","America/Argentina/Rio_Gallegos|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343434343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 g0p0 10M0 ako0 7B0 8zb0 uL0","America/Argentina/Salta|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|01212121212121212121212121212121212121212134343434543432343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 j3c0 uL0","America/Argentina/San_Juan|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|01212121212121212121212121212121212121212134343434534343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Qn0 qO0 16n0 Rb0 1wp0 TX0 g0p0 10M0 ak00 m10 8lb0 uL0","America/Argentina/San_Luis|CMT ART ARST ART ARST WART WARST|4g.M 40 30 30 20 40 30|01212121212121212121212121212121212121212134343456536353465653|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 XX0 1q20 SL0 AN0 kin0 10M0 ak00 m10 8lb0 8L0 jd0 1qN0 WL0 1qN0","America/Argentina/Tucuman|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|012121212121212121212121212121212121212121343434345434323534343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wq0 Ra0 1wp0 TX0 g0p0 10M0 ako0 4N0 8BX0 uL0 1qN0 WL0","America/Argentina/Ushuaia|CMT ART ARST ART ARST WART|4g.M 40 30 30 20 40|0121212121212121212121212121212121212121213434343434343235343|-20UHH.c pKnH.c Mn0 1iN0 Tb0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 1C10 LX0 1C10 LX0 1C10 LX0 1C10 Mn0 MN0 2jz0 MN0 4lX0 u10 5Lb0 1pB0 Fnz0 u10 uL0 1vd0 SL0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 zvd0 Bz0 1tB0 TX0 1wp0 Rb0 1wp0 Rb0 1wp0 TX0 g0p0 10M0 ajA0 8p0 8zb0 uL0","America/Curacao|LMT ANT AST|4z.L 4u 40|012|-2kV7o.d 28KLS.d|15e4","America/Asuncion|AMT PYT PYT PYST|3O.E 40 30 30|012131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313|-1x589.k 1DKM9.k 3CL0 3Dd0 10L0 1pB0 10n0 1pB0 10n0 1pB0 1cL0 1dd0 1db0 1dd0 1cL0 1dd0 1cL0 1dd0 1cL0 1dd0 1db0 1dd0 1cL0 1dd0 1cL0 1dd0 1cL0 1dd0 1db0 1dd0 1cL0 1lB0 14n0 1dd0 1cL0 1fd0 WL0 1rd0 1aL0 1dB0 Xz0 1qp0 Xb0 1qN0 10L0 1rB0 TX0 1tB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 WN0 1qL0 11B0 1nX0 1ip0 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 TX0 1tB0 19X0 1a10 1fz0 1a10 1fz0 1cN0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1ip0 17b0 1ip0 17b0 1ip0|28e5","America/Atikokan|CST CDT CWT CPT EST|60 50 50 50 50|0101234|-25TQ0 1in0 Rnb0 3je0 8x30 iw0|28e2","America/Bahia|LMT BRT BRST|2y.4 30 20|01212121212121212121212121212121212121212121212121212121212121|-2glxp.U HdLp.U 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 l5B0 Rb0|27e5","America/Bahia_Banderas|LMT MST CST PST MDT CDT|71 70 60 80 60 50|0121212131414141414141414141414141414152525252525252525252525252525252525252525252525252525252|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nW0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|84e3","America/Barbados|LMT BMT AST ADT|3W.t 3W.t 40 30|01232323232|-1Q0I1.v jsM0 1ODC1.v IL0 1ip0 17b0 1ip0 17b0 1ld0 13b0|28e4","America/Belem|LMT BRT BRST|3d.U 30 20|012121212121212121212121212121|-2glwK.4 HdKK.4 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0|20e5","America/Belize|LMT CST CHDT CDT|5Q.M 60 5u 50|01212121212121212121212121212121212121212121212121213131|-2kBu7.c fPA7.c Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1wou Rbu 1zcu Onu 1zcu Onu 1zcu Rbu 1wou Rbu 1f0Mu qn0 lxB0 mn0|57e3","America/Blanc-Sablon|AST ADT AWT APT|40 30 30 30|010230|-25TS0 1in0 UGp0 8x50 iu0|11e2","America/Boa_Vista|LMT AMT AMST|42.E 40 30|0121212121212121212121212121212121|-2glvV.k HdKV.k 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 smp0 WL0 1tB0 2L0|62e2","America/Bogota|BMT COT COST|4U.g 50 40|0121|-2eb73.I 38yo3.I 2en0|90e5","America/Boise|PST PDT MST MWT MPT MDT|80 70 70 60 60 60|0101023425252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-261q0 1nX0 11B0 1nX0 8C10 JCL0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 Dd0 1Kn0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e4","America/Cambridge_Bay|-00 MST MWT MPT MDDT MDT CST CDT EST|0 70 60 60 50 60 60 50 50|0123141515151515151515151515151515151515151515678651515151515151515151515151515151515151515151515151515151515151515151515151|-21Jc0 RO90 8x20 ix0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11A0 1nX0 2K0 WQ0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|15e2","America/Campo_Grande|LMT AMT AMST|3C.s 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwl.w HdLl.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 1C10 Lz0 1Ip0 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0|77e4","America/Cancun|LMT CST EST EDT CDT|5L.4 60 50 40 50|0123232341414141414141414141414141414141412|-1UQG0 2q2o0 yLB0 1lb0 14p0 1lb0 14p0 Lz0 xB0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 Dd0|63e4","America/Caracas|CMT VET VET|4r.E 4u 40|01212|-2kV7w.k 28KM2.k 1IwOu kqo0|29e5","America/Cayenne|LMT GFT GFT|3t.k 40 30|012|-2mrwu.E 2gWou.E|58e3","America/Panama|CMT EST|5j.A 50|01|-2uduE.o|15e5","America/Chicago|CST CDT EST CWT CPT|60 50 50 50 50|01010101010101010101010101010101010102010101010103401010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 1wp0 TX0 WN0 1qL0 1cN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 11B0 1Hz0 14p0 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|92e5","America/Chihuahua|LMT MST CST CDT MDT|74.k 70 60 50 60|0121212323241414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|81e4","America/Costa_Rica|SJMT CST CDT|5A.d 60 50|0121212121|-1Xd6n.L 2lu0n.L Db0 1Kp0 Db0 pRB0 15b0 1kp0 mL0|12e5","America/Creston|MST PST|70 80|010|-29DR0 43B0|53e2","America/Cuiaba|LMT AMT AMST|3I.k 40 30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwf.E HdLf.E 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 4a10 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0|54e4","America/Danmarkshavn|LMT WGT WGST GMT|1e.E 30 20 0|01212121212121212121212121212121213|-2a5WJ.k 2z5fJ.k 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 DC0|8","America/Dawson|YST YDT YWT YPT YDDT PST PDT|90 80 80 80 70 80 70|0101023040565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-25TN0 1in0 1o10 13V0 Ser0 8x00 iz0 LCL0 1fA0 jrA0 fNd0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|13e2","America/Dawson_Creek|PST PDT PWT PPT MST|80 70 70 70 70|0102301010101010101010101010101010101010101010101010101014|-25TO0 1in0 UGp0 8x10 iy0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 ML0|12e3","America/Denver|MST MDT MWT MPT|70 60 60 60|01010101023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261r0 1nX0 11B0 1nX0 11B0 1qL0 WN0 mn0 Ord0 8x20 ix0 LCN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|26e5","America/Detroit|LMT CST EST EWT EPT EDT|5w.b 60 50 40 40 40|01234252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-2Cgir.N peqr.N 156L0 8x40 iv0 6fd0 11z0 Jy10 SL0 dnB0 1cL0 s10 1Vz0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|37e5","America/Edmonton|LMT MST MDT MWT MPT|7x.Q 70 60 60 60|01212121212121341212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2yd4q.8 shdq.8 1in0 17d0 hz0 2dB0 1fz0 1a10 11z0 1qN0 WL0 1qN0 11z0 IGN0 8x20 ix0 3NB0 11z0 LFB0 1cL0 3Cp0 1cL0 66N0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|10e5","America/Eirunepe|LMT ACT ACST AMT|4D.s 50 40 40|0121212121212121212121212121212131|-2glvk.w HdLk.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 dPB0 On0 yTd0 d5X0|31e3","America/El_Salvador|LMT CST CDT|5U.M 60 50|012121|-1XiG3.c 2Fvc3.c WL0 1qN0 WL0|11e5","America/Tijuana|LMT MST PST PDT PWT PPT|7M.4 70 80 70 70 70|012123245232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQE0 4PX0 8mM0 8lc0 SN0 1cL0 pHB0 83r0 zI0 5O10 1Rz0 cOP0 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 BUp0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|20e5","America/Fort_Nelson|PST PDT PWT PPT MST|80 70 70 70 70|01023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010104|-25TO0 1in0 UGp0 8x10 iy0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0|39e2","America/Fort_Wayne|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|010101023010101010101010101040454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 QI10 Db0 RB0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 5Tz0 1o10 qLb0 1cL0 1cN0 1cL0 1qhd0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Fortaleza|LMT BRT BRST|2y 30 20|0121212121212121212121212121212121212121|-2glxq HdLq 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 5z0 2mN0 On0|34e5","America/Glace_Bay|LMT AST ADT AWT APT|3X.M 40 30 30 30|012134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsI0.c CwO0.c 1in0 UGp0 8x50 iu0 iq10 11z0 Jg10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|19e3","America/Godthab|LMT WGT WGST|3q.U 30 20|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a5Ux.4 2z5dx.4 19U0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e3","America/Goose_Bay|NST NDT NST NDT NWT NPT AST ADT ADDT|3u.Q 2u.Q 3u 2u 2u 2u 40 30 20|010232323232323245232323232323232323232323232323232323232326767676767676767676767676767676767676767676768676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-25TSt.8 1in0 DXb0 2HbX.8 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 WL0 1qN0 WL0 1qN0 7UHu itu 1tB0 WL0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1tB0 WL0 1ld0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 S10 g0u 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14n1 1lb0 14p0 1nW0 11C0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|76e2","America/Grand_Turk|KMT EST EDT AST|57.b 50 40 40|0121212121212121212121212121212121212121212121212121212121212121212121212123|-2l1uQ.N 2HHBQ.N 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|37e2","America/Guatemala|LMT CST CDT|62.4 60 50|0121212121|-24KhV.U 2efXV.U An0 mtd0 Nz0 ifB0 17b0 zDB0 11z0|13e5","America/Guayaquil|QMT ECT|5e 50|01|-1yVSK|27e5","America/Guyana|LMT GBGT GYT GYT GYT|3Q.E 3J 3J 30 40|01234|-2dvU7.k 24JzQ.k mlc0 Bxbf|80e4","America/Halifax|LMT AST ADT AWT APT|4e.o 40 30 30 30|0121212121212121212121212121212121212121212121212134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsHJ.A xzzJ.A 1db0 3I30 1in0 3HX0 IL0 1E10 ML0 1yN0 Pb0 1Bd0 Mn0 1Bd0 Rz0 1w10 Xb0 1w10 LX0 1w10 Xb0 1w10 Lz0 1C10 Jz0 1E10 OL0 1yN0 Un0 1qp0 Xb0 1qp0 11X0 1w10 Lz0 1HB0 LX0 1C10 FX0 1w10 Xb0 1qp0 Xb0 1BB0 LX0 1td0 Xb0 1qp0 Xb0 Rf0 8x50 iu0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 3Qp0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 3Qp0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 6i10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|39e4","America/Havana|HMT CST CDT|5t.A 50 40|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1Meuu.o 72zu.o ML0 sld0 An0 1Nd0 Db0 1Nd0 An0 6Ep0 An0 1Nd0 An0 JDd0 Mn0 1Ap0 On0 1fd0 11X0 1qN0 WL0 1wp0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 14n0 1ld0 14L0 1kN0 15b0 1kp0 1cL0 1cN0 1fz0 1a10 1fz0 1fB0 11z0 14p0 1nX0 11B0 1nX0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 1a10 1in0 1a10 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 17c0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 11A0 6i00 Rc0 1wo0 U00 1tA0 Rc0 1wo0 U00 1wo0 U00 1zc0 U00 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0|21e5","America/Hermosillo|LMT MST CST PST MDT|7n.Q 70 60 80 60|0121212131414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0|64e4","America/Indiana/Knox|CST CDT CWT CPT EST|60 50 50 50 50|0101023010101010101010101010101010101040101010101010101010101010101010101010101010101010141010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 3NB0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 11z0 1o10 11z0 1o10 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 3Cn0 8wp0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 z8o0 1o00 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Indiana/Marengo|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101023010101010101010104545454545414545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 dyN0 11z0 6fd0 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 jrz0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1VA0 LA0 1BX0 1e6p0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Indiana/Petersburg|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010104010101010101010101010141014545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 njX0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 3Fb0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 19co0 1o00 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Indiana/Tell_City|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010454541010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 g0p0 11z0 1o10 11z0 1qL0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 caL0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Indiana/Vevay|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|010102304545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 kPB0 Awn0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1lnd0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Indiana/Vincennes|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010454541014545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 g0p0 11z0 1o10 11z0 1qL0 WN0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 caL0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Indiana/Winamac|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|01010230101010101010101010101010101010454541054545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 jrz0 1cL0 1cN0 1cL0 1qhd0 1o00 Rd0 1za0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Inuvik|-00 PST PDDT MST MDT|0 80 60 70 60|0121343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-FnA0 tWU0 1fA0 wPe0 2pz0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|35e2","America/Iqaluit|-00 EWT EPT EST EDDT EDT CST CDT|0 40 40 50 30 40 60 50|01234353535353535353535353535353535353535353567353535353535353535353535353535353535353535353535353535353535353535353535353|-16K00 7nX0 iv0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11C0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|67e2","America/Jamaica|KMT EST EDT|57.b 50 40|0121212121212121212121|-2l1uQ.N 2uM1Q.N 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0|94e4","America/Juneau|PST PWT PPT PDT YDT YST AKST AKDT|80 70 70 70 80 90 90 80|01203030303030303030303030403030356767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cM0 1cM0 1cL0 1cN0 1fz0 1a10 1fz0 co0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|33e3","America/Kentucky/Louisville|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101010102301010101010101010101010101454545454545414545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 3Fd0 Nb0 LPd0 11z0 RB0 8x30 iw0 Bb0 10N0 2bB0 8in0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 xz0 gso0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1VA0 LA0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Kentucky/Monticello|CST CDT CWT CPT EST EDT|60 50 50 50 50 40|0101023010101010101010101010101010101010101010101010101010101010101010101454545454545454545454545454545454545454545454545454545454545454545454545454|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 SWp0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/La_Paz|CMT BOST BOT|4w.A 3w.A 40|012|-1x37r.o 13b0|19e5","America/Lima|LMT PET PEST|58.A 50 40|0121212121212121|-2tyGP.o 1bDzP.o zX0 1aN0 1cL0 1cN0 1cL0 1PrB0 zX0 1O10 zX0 6Gp0 zX0 98p0 zX0|11e6","America/Los_Angeles|PST PDT PWT PPT|80 70 70 70|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261q0 1nX0 11B0 1nX0 SgN0 8x10 iy0 5Wp0 1Vb0 3dB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|15e6","America/Maceio|LMT BRT BRST|2m.Q 30 20|012121212121212121212121212121212121212121|-2glxB.8 HdLB.8 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 dMN0 Lz0 8Q10 WL0 1tB0 5z0 2mN0 On0|93e4","America/Managua|MMT CST EST CDT|5J.c 60 50 50|0121313121213131|-1quie.M 1yAMe.M 4mn0 9Up0 Dz0 1K10 Dz0 s3F0 1KH0 DB0 9In0 k8p0 19X0 1o30 11y0|22e5","America/Manaus|LMT AMT AMST|40.4 40 30|01212121212121212121212121212121|-2glvX.U HdKX.U 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 dPB0 On0|19e5","America/Martinique|FFMT AST ADT|44.k 40 30|0121|-2mPTT.E 2LPbT.E 19X0|39e4","America/Matamoros|LMT CST CDT|6E 60 50|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1UQG0 2FjC0 1nX0 i6p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|45e4","America/Mazatlan|LMT MST CST PST MDT|75.E 70 60 80 60|0121212131414141414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 otX0 gmN0 P2N0 13Vd0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|44e4","America/Menominee|CST CDT CWT CPT EST|60 50 50 50 50|01010230101041010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 1o10 11z0 LCN0 1fz0 6410 9Jb0 1cM0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|85e2","America/Merida|LMT CST EST CDT|5W.s 60 50 50|0121313131313131313131313131313131313131313131313131313131313131313131313131313131313131|-1UQG0 2q2o0 2hz0 wu30 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|11e5","America/Metlakatla|PST PWT PPT PDT AKST AKDT|80 70 70 70 90 80|0120303030303030303030303030303030454545454545454545454545454545454545454545454|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1hU10 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|14e2","America/Mexico_City|LMT MST CST CDT CWT|6A.A 70 60 50 50|012121232324232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 gEn0 TX0 3xd0 Jb0 6zB0 SL0 e5d0 17b0 1Pff0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|20e6","America/Miquelon|LMT AST PMST PMDT|3I.E 40 30 20|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2mKkf.k 2LTAf.k gQ10 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|61e2","America/Moncton|EST AST ADT AWT APT|50 40 30 30 30|012121212121212121212134121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2IsH0 CwN0 1in0 zAo0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1Nd0 An0 1K10 Lz0 1zB0 NX0 1u10 Wn0 S20 8x50 iu0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 3Cp0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14n1 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 ReX 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|64e3","America/Monterrey|LMT CST CDT|6F.g 60 50|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1UQG0 2FjC0 1nX0 i6p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0|41e5","America/Montevideo|MMT UYT UYHST UYST UYT UYHST|3I.I 3u 30 20 30 2u|012121212121212121212121213434343434345454543453434343434343434343434343434343434343434|-20UIf.g 8jzJ.g 1cLu 1dcu 1cLu 1dcu 1cLu ircu 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 1qMu WLu 1qMu WLu 1qMu 11zu 1o0u 11zu NAu 11bu 2iMu zWu Dq10 19X0 pd0 jz0 cm10 19X0 1fB0 1on0 11d0 1oL0 1nB0 1fzu 1aou 1fzu 1aou 1fzu 3nAu Jb0 3MN0 1SLu 4jzu 2PB0 Lb0 3Dd0 1pb0 ixd0 An0 1MN0 An0 1wp0 On0 1wp0 Rb0 1zd0 On0 1wp0 Rb0 s8p0 1fB0 1ip0 11z0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 14n0 1ld0 14n0 1ld0 14n0 1o10 11z0 1o10 11z0 1o10 11z0|17e5","America/Toronto|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101012301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TR0 1in0 11Wu 1nzu 1fD0 WJ0 1wr0 Nb0 1Ap0 On0 1zd0 On0 1wp0 TX0 1tB0 TX0 1tB0 TX0 1tB0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 4kM0 8x40 iv0 1o10 11z0 1nX0 11z0 1o10 11z0 1o10 1qL0 11D0 1nX0 11B0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|65e5","America/Nassau|LMT EST EDT|59.u 50 40|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2kNuO.u 26XdO.u 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|24e4","America/New_York|EST EDT EWT EPT|50 40 40 40|01010101010101010101010101010101010101010101010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 11B0 1qL0 1a10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 RB0 8x40 iv0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|21e6","America/Nipigon|EST EDT EWT EPT|50 40 40 40|010123010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TR0 1in0 Rnb0 3je0 8x40 iv0 19yN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|16e2","America/Nome|NST NWT NPT BST BDT YST AKST AKDT|b0 a0 a0 b0 a0 90 90 80|012034343434343434343434343434343456767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676|-17SX0 8wW0 iB0 Qlb0 52O0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cl0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|38e2","America/Noronha|LMT FNT FNST|29.E 20 10|0121212121212121212121212121212121212121|-2glxO.k HdKO.k 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 2L0 2pB0 On0|30e2","America/North_Dakota/Beulah|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101014545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Oo0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/North_Dakota/Center|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101014545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14o0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/North_Dakota/New_Salem|MST MDT MWT MPT CST CDT|70 60 60 60 60 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101454545454545454545454545454545454545454545454545454545454545454545454|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14o0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","America/Ojinaga|LMT MST CST CDT MDT|6V.E 70 60 50 60|0121212323241414141414141414141414141414141414141414141414141414141414141414141414141414141|-1UQF0 deL0 8lc0 17c0 10M0 1dd0 2zQN0 1lb0 14p0 1lb0 14q0 1lb0 14p0 1nX0 11B0 1nX0 1fB0 WL0 1fB0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 U10 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e3","America/Pangnirtung|-00 AST AWT APT ADDT ADT EDT EST CST CDT|0 40 30 30 20 30 40 50 60 50|012314151515151515151515151515151515167676767689767676767676767676767676767676767676767676767676767676767676767676767676767|-1XiM0 PnG0 8x50 iu0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1o00 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11C0 1nX0 11A0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|14e2","America/Paramaribo|LMT PMT PMT NEGT SRT SRT|3E.E 3E.Q 3E.A 3u 3u 30|012345|-2nDUj.k Wqo0.c qanX.I 1dmLN.o lzc0|24e4","America/Phoenix|MST MDT MWT|70 60 60|01010202010|-261r0 1nX0 11B0 1nX0 SgN0 4Al1 Ap0 1db0 SWqX 1cL0|42e5","America/Port-au-Prince|PPMT EST EDT|4N 50 40|01212121212121212121212121212121212121212121|-28RHb 2FnMb 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14q0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 i6n0 1nX0 11B0 1nX0 d430 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e5","America/Rio_Branco|LMT ACT ACST AMT|4v.c 50 40 40|01212121212121212121212121212131|-2glvs.M HdLs.M 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 NBd0 d5X0|31e4","America/Porto_Velho|LMT AMT AMST|4f.A 40 30|012121212121212121212121212121|-2glvI.o HdKI.o 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0|37e4","America/Puerto_Rico|AST AWT APT|40 30 30|0120|-17lU0 7XT0 iu0|24e5","America/Rainy_River|CST CDT CWT CPT|60 50 50 50|010123010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TQ0 1in0 Rnb0 3je0 8x30 iw0 19yN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|842","America/Rankin_Inlet|-00 CST CDDT CDT EST|0 60 40 50 50|012131313131313131313131313131313131313131313431313131313131313131313131313131313131313131313131313131313131313131313131|-vDc0 keu0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|26e2","America/Recife|LMT BRT BRST|2j.A 30 20|0121212121212121212121212121212121212121|-2glxE.o HdLE.o 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 nsp0 WL0 1tB0 2L0 2pB0 On0|33e5","America/Regina|LMT MST MDT MWT MPT CST|6W.A 70 60 60 60 60|012121212121212121212121341212121212121212121212121215|-2AD51.o uHe1.o 1in0 s2L0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 66N0 1cL0 1cN0 19X0 1fB0 1cL0 1fB0 1cL0 1cN0 1cL0 M30 8x20 ix0 1ip0 1cL0 1ip0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 3NB0 1cL0 1cN0|19e4","America/Resolute|-00 CST CDDT CDT EST|0 60 40 50 50|012131313131313131313131313131313131313131313431313131313431313131313131313131313131313131313131313131313131313131313131|-SnA0 GWS0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|229","America/Santarem|LMT AMT AMST BRT|3C.M 40 30 30|0121212121212121212121212121213|-2glwl.c HdLl.c 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 qe10 xb0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 NBd0|21e4","America/Santiago|SMT CLT CLT CLST CLST|4G.K 50 40 40 30|010203131313131212421242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424|-2q2jh.e fJAh.e 5knG.K 1Vzh.e jRAG.K 1pbh.e 11d0 1oL0 11d0 1oL0 11d0 1oL0 11d0 1pb0 11d0 nHX0 op0 9Bz0 jb0 1oN0 ko0 Qeo0 WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|62e5","America/Santo_Domingo|SDMT EST EDT EHDT AST|4E 50 40 4u 40|01213131313131414|-1ttjk 1lJMk Mn0 6sp0 Lbu 1Cou yLu 1RAu wLu 1QMu xzu 1Q0u xXu 1PAu 13jB0 e00|29e5","America/Sao_Paulo|LMT BRT BRST|36.s 30 20|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-2glwR.w HdKR.w 1cc0 1e10 1bX0 Ezd0 So0 1vA0 Mn0 1BB0 ML0 1BB0 zX0 pTd0 PX0 2ep0 nz0 1C10 zX0 1C10 LX0 1C10 Mn0 H210 Rb0 1tB0 IL0 1Fd0 FX0 1EN0 FX0 1HB0 Lz0 1EN0 Lz0 1C10 IL0 1HB0 Db0 1HB0 On0 1zd0 On0 1zd0 Lz0 1zd0 Rb0 1wN0 Wn0 1tB0 Rb0 1tB0 WL0 1tB0 Rb0 1zd0 On0 1HB0 FX0 1C10 Lz0 1Ip0 HX0 1zd0 On0 1HB0 IL0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1zd0 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0 On0 1zd0 On0 1zd0 On0 1C10 Lz0 1C10 Lz0 1C10 Lz0 1C10 On0 1zd0 Rb0 1wp0 On0 1C10 Lz0 1C10 On0 1zd0|20e6","America/Scoresbysund|LMT CGT CGST EGST EGT|1r.Q 20 10 0 10|0121343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-2a5Ww.8 2z5ew.8 1a00 1cK0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|452","America/Sitka|PST PWT PPT PDT YST AKST AKDT|80 70 70 70 90 90 80|01203030303030303030303030303030345656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-17T20 8x10 iy0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 co0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|90e2","America/St_Johns|NST NDT NST NDT NWT NPT NDDT|3u.Q 2u.Q 3u 2u 2u 2u 1u|01010101010101010101010101010101010102323232323232324523232323232323232323232323232323232323232323232323232323232323232323232323232323232326232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-28oit.8 14L0 1nB0 1in0 1gm0 Dz0 1JB0 1cL0 1cN0 1cL0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 1cL0 1cN0 1cL0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 19X0 1fB0 1cL0 1fB0 19X0 1fB0 19X0 10O0 eKX.8 19X0 1iq0 WL0 1qN0 WL0 1qN0 WL0 1tB0 TX0 1tB0 WL0 1qN0 WL0 1qN0 7UHu itu 1tB0 WL0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1tB0 WL0 1ld0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14n1 1lb0 14p0 1nW0 11C0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zcX Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|11e4","America/Swift_Current|LMT MST MDT MWT MPT CST|7b.k 70 60 60 60 60|012134121212121212121215|-2AD4M.E uHdM.E 1in0 UGp0 8x20 ix0 1o10 17b0 1ip0 11z0 1o10 11z0 1o10 11z0 isN0 1cL0 3Cp0 1cL0 1cN0 11z0 1qN0 WL0 pMp0|16e3","America/Tegucigalpa|LMT CST CDT|5M.Q 60 50|01212121|-1WGGb.8 2ETcb.8 WL0 1qN0 WL0 GRd0 AL0|11e5","America/Thule|LMT AST ADT|4z.8 40 30|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a5To.Q 31NBo.Q 1cL0 1cN0 1cL0 1fB0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|656","America/Thunder_Bay|CST EST EWT EPT EDT|60 50 40 40 40|0123141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141|-2q5S0 1iaN0 8x40 iv0 XNB0 1cL0 1cN0 1fz0 1cN0 1cL0 3Cp0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|11e4","America/Vancouver|PST PDT PWT PPT|80 70 70 70|0102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-25TO0 1in0 UGp0 8x10 iy0 1o10 17b0 1ip0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e5","America/Whitehorse|YST YDT YWT YPT YDDT PST PDT|90 80 80 80 70 80 70|0101023040565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565|-25TN0 1in0 1o10 13V0 Ser0 8x00 iz0 LCL0 1fA0 3NA0 vrd0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|23e3","America/Winnipeg|CST CDT CWT CPT|60 50 50 50|010101023010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aIi0 WL0 3ND0 1in0 Jap0 Rb0 aCN0 8x30 iw0 1tB0 11z0 1ip0 11z0 1o10 11z0 1o10 11z0 1rd0 10L0 1op0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 1cL0 1cN0 11z0 6i10 WL0 6i10 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1o00 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1o00 11A0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|66e4","America/Yakutat|YST YWT YPT YDT AKST AKDT|90 80 80 80 90 80|01203030303030303030303030303030304545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-17T10 8x00 iz0 Vo10 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 cn0 10q0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|642","America/Yellowknife|-00 MST MWT MPT MDDT MDT|0 70 60 60 50 60|012314151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151|-1pdA0 hix0 8x20 ix0 LCL0 1fA0 zgO0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|19e3","Antarctica/Casey|-00 AWST CAST|0 -80 -b0|012121|-2q00 1DjS0 T90 40P0 KL0|10","Antarctica/Davis|-00 DAVT DAVT|0 -70 -50|01012121|-vyo0 iXt0 alj0 1D7v0 VB0 3Wn0 KN0|70","Antarctica/DumontDUrville|-00 PMT DDUT|0 -a0 -a0|0102|-U0o0 cfq0 bFm0|80","Antarctica/Macquarie|AEST AEDT -00 MIST|-a0 -b0 0 -b0|0102010101010101010101010101010101010101010101010101010101010101010101010101010101010101013|-29E80 19X0 4SL0 1ayy0 Lvs0 1cM0 1o00 Rc0 1wo0 Rc0 1wo0 U00 1wo0 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0|1","Antarctica/Mawson|-00 MAWT MAWT|0 -60 -50|012|-CEo0 2fyk0|60","Pacific/Auckland|NZMT NZST NZST NZDT|-bu -cu -c0 -d0|01020202020202020202020202023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323|-1GCVu Lz0 1tB0 11zu 1o0u 11zu 1o0u 11zu 1o0u 14nu 1lcu 14nu 1lcu 1lbu 11Au 1nXu 11Au 1nXu 11Au 1nXu 11Au 1nXu 11Au 1qLu WMu 1qLu 11Au 1n1bu IM0 1C00 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1qM0 14o0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1io0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|14e5","Antarctica/Palmer|-00 ARST ART ART ARST CLT CLST|0 30 40 30 20 40 30|0121212121234356565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656|-cao0 nD0 1vd0 SL0 1vd0 17z0 1cN0 1fz0 1cN0 1cL0 1cN0 asn0 Db0 jsN0 14N0 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|40","Antarctica/Rothera|-00 ROTT|0 30|01|gOo0|130","Antarctica/Syowa|-00 SYOT|0 -30|01|-vs00|20","Antarctica/Troll|-00 UTC CEST|0 0 -20|01212121212121212121212121212121212121212121212121212121212121212121|1puo0 hd0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|40","Antarctica/Vostok|-00 VOST|0 -60|01|-tjA0|25","Europe/Oslo|CET CEST|-10 -20|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2awM0 Qm0 W6o0 5pf0 WM0 1fA0 1cM0 1cM0 1cM0 1cM0 wJc0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1qM0 WM0 zpc0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|62e4","Asia/Riyadh|LMT AST|-36.Q -30|01|-TvD6.Q|57e5","Asia/Almaty|LMT +05 +06 +07|-57.M -50 -60 -70|012323232323232323232321232323232323232323232323232|-1Pc57.M eUo7.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0|15e5","Asia/Amman|LMT EET EEST|-2n.I -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1yW2n.I 1HiMn.I KL0 1oN0 11b0 1oN0 11b0 1pd0 1dz0 1cp0 11b0 1op0 11b0 fO10 1db0 1e10 1cL0 1cN0 1cL0 1cN0 1fz0 1pd0 10n0 1ld0 14n0 1hB0 15b0 1ip0 19X0 1cN0 1cL0 1cN0 17b0 1ld0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1So0 y00 1fc0 1dc0 1co0 1dc0 1cM0 1cM0 1cM0 1o00 11A0 1lc0 17c0 1cM0 1cM0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 4bX0 Dd0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|25e5","Asia/Anadyr|LMT ANAT ANAT ANAST ANAST ANAST ANAT|-bN.U -c0 -d0 -e0 -d0 -c0 -b0|01232414141414141414141561414141414141414141414141414141414141561|-1PcbN.U eUnN.U 23CL0 1db0 1cN0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0|13e3","Asia/Aqtau|LMT +04 +05 +06|-3l.4 -40 -50 -60|012323232323232323232123232312121212121212121212|-1Pc3l.4 eUnl.4 24PX0 2pX0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0|15e4","Asia/Aqtobe|LMT +04 +05 +06|-3M.E -40 -50 -60|0123232323232323232321232323232323232323232323232|-1Pc3M.E eUnM.E 23CL0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0|27e4","Asia/Ashgabat|LMT ASHT ASHT ASHST ASHST TMT TMT|-3R.w -40 -50 -60 -50 -40 -50|012323232323232323232324156|-1Pc3R.w eUnR.w 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 ba0 xC0|41e4","Asia/Baghdad|BMT AST ADT|-2V.A -30 -40|012121212121212121212121212121212121212121212121212121|-26BeV.A 2ACnV.A 11b0 1cp0 1dz0 1dd0 1db0 1cN0 1cp0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1de0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0 1dc0 1dc0 1cM0 1dc0 1cM0 1dc0 1cM0 1dc0|66e5","Asia/Qatar|LMT GST AST|-3q.8 -40 -30|012|-21Jfq.8 27BXq.8|96e4","Asia/Baku|LMT BAKT BAKT BAKST BAKST AZST AZT AZT AZST|-3j.o -30 -40 -50 -40 -40 -30 -40 -50|01232323232323232323232456578787878787878787878787878787878787878787|-1Pc3j.o 1jUoj.o WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 10K0 c30 1cM0 1cM0 8wq0 1o00 11z0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00|27e5","Asia/Bangkok|BMT ICT|-6G.4 -70|01|-218SG.4|15e6","Asia/Barnaul|LMT +06 +07 +08|-5z -60 -70 -80|0123232323232323232323212323232321212121212121212121212121212121212|-21S5z pCnz 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 p90 LE0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0","Asia/Beirut|EET EEST|-20 -30|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-21aq0 1on0 1410 1db0 19B0 1in0 1ip0 WL0 1lQp0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 q6N0 En0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1op0 11b0 dA10 17b0 1iN0 17b0 1iN0 17b0 1iN0 17b0 1vB0 SL0 1mp0 13z0 1iN0 17b0 1iN0 17b0 1jd0 12n0 1a10 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0|22e5","Asia/Bishkek|LMT FRUT FRUT FRUST FRUST KGT KGST KGT|-4W.o -50 -60 -70 -60 -50 -60 -60|01232323232323232323232456565656565656565656565656567|-1Pc4W.o eUnW.o 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 11c0 1tX0 17b0 1ip0 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1cPu 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 T8u|87e4","Asia/Brunei|LMT BNT BNT|-7D.E -7u -80|012|-1KITD.E gDc9.E|42e4","Asia/Kolkata|HMT BURT IST IST|-5R.k -6u -5u -6u|01232|-18LFR.k 1unn.k HB0 7zX0|15e6","Asia/Chita|LMT YAKT YAKT YAKST YAKST YAKT IRKT|-7x.Q -80 -90 -a0 -90 -a0 -80|0123232323232323232323241232323232323232323232323232323232323232562|-21Q7x.Q pAnx.Q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3re0|33e4","Asia/Choibalsan|LMT ULAT ULAT CHOST CHOT CHOT CHOST|-7C -70 -80 -a0 -90 -80 -90|0123434343434343434343434343434343434343434343456565656565656565656565656565656565656565656565|-2APHC 2UkoC cKn0 1da0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 3Db0 h1f0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|38e3","Asia/Shanghai|CST CDT|-80 -90|01010101010101010|-1c1I0 LX0 16p0 1jz0 1Myp0 Rb0 1o10 11z0 1o10 11z0 1qN0 11z0 1o10 11z0 1o10 11z0|23e6","Asia/Colombo|MMT IST IHST IST LKT LKT|-5j.w -5u -60 -6u -6u -60|01231451|-2zOtj.w 1rFbN.w 1zzu 7Apu 23dz0 11zu n3cu|22e5","Asia/Dhaka|HMT BURT IST DACT BDT BDST|-5R.k -6u -5u -60 -60 -70|01213454|-18LFR.k 1unn.k HB0 m6n0 LqMu 1x6n0 1i00|16e6","Asia/Damascus|LMT EET EEST|-2p.c -20 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-21Jep.c Hep.c 17b0 1ip0 17b0 1ip0 17b0 1ip0 19X0 1xRB0 11X0 1oN0 10L0 1pB0 11b0 1oN0 10L0 1mp0 13X0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 1pd0 11b0 1oN0 Nb0 1AN0 Nb0 bcp0 19X0 1gp0 19X0 3ld0 1xX0 Vd0 1Bz0 Sp0 1vX0 10p0 1dz0 1cN0 1cL0 1db0 1db0 1g10 1an0 1ap0 1db0 1fd0 1db0 1cN0 1db0 1dd0 1db0 1cp0 1dz0 1c10 1dX0 1cN0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1db0 1cN0 1db0 1cN0 19z0 1fB0 1qL0 11B0 1on0 Wp0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 1qL0 WN0 1qL0|26e5","Asia/Dili|LMT TLT JST TLT WITA|-8m.k -80 -90 -90 -80|012343|-2le8m.k 1dnXm.k 8HA0 1ew00 Xld0|19e4","Asia/Dubai|LMT GST|-3F.c -40|01|-21JfF.c|39e5","Asia/Dushanbe|LMT DUST DUST DUSST DUSST TJT|-4z.c -50 -60 -70 -60 -50|0123232323232323232323245|-1Pc4z.c eUnz.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 14N0|76e4","Asia/Gaza|EET EET EEST IST IDT|-20 -30 -30 -20 -30|010101010102020202020202020202023434343434343434343434343430202020202020202020202020202020202020202020202020202020202020202020202020202020202020|-1c2q0 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 pBd0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 dW0 hfB0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 M10 C00 17c0 1io0 17c0 1io0 17c0 1o00 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 17c0 1io0 18N0 1bz0 19z0 1gp0 1610 1iL0 11z0 1o10 14o0 1lA1 SKX 1xd1 MKX 1AN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0|18e5","Asia/Hebron|EET EET EEST IST IDT|-20 -30 -30 -20 -30|01010101010202020202020202020202343434343434343434343434343020202020202020202020202020202020202020202020202020202020202020202020202020202020202020|-1c2q0 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 pBd0 Vz0 1oN0 11b0 1oO0 10N0 1pz0 10N0 1pb0 10N0 1pb0 10N0 1pb0 10N0 1pz0 10N0 1pb0 10N0 1pb0 11d0 1oL0 dW0 hfB0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 M10 C00 17c0 1io0 17c0 1io0 17c0 1o00 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 17c0 1io0 18N0 1bz0 19z0 1gp0 1610 1iL0 12L0 1mN0 14o0 1lc0 Tb0 1xd1 MKX bB0 cn0 1cN0 1a00 1fA0 1cL0 1cN0 1nX0 1210 1nz0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1ny0 1220 1qm0 1220 1ny0 1220 1ny0 1220 1ny0|25e4","Asia/Ho_Chi_Minh|LMT PLMT ICT IDT JST|-76.E -76.u -70 -80 -90|0123423232|-2yC76.E bK00.a 1h7b6.u 5lz0 18o0 3Oq0 k5b0 aW00 BAM0|90e5","Asia/Hong_Kong|LMT HKT HKST JST|-7A.G -80 -90 -90|0121312121212121212121212121212121212121212121212121212121212121212121|-2CFHA.G 1sEP6.G 1cL0 ylu 93X0 1qQu 1tX0 Rd0 1In0 NB0 1cL0 11B0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1kL0 14N0 1nX0 U10 1tz0 U10 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1wn0 Rd0 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 17d0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 s10 1Vz0 1cN0 1cL0 1cN0 1cL0 6fd0 14n0|73e5","Asia/Hovd|LMT HOVT HOVT HOVST|-66.A -60 -70 -80|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2APG6.A 2Uko6.A cKn0 1db0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 kEp0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|81e3","Asia/Irkutsk|IMT IRKT IRKT IRKST IRKST IRKT|-6V.5 -70 -80 -90 -80 -90|012323232323232323232324123232323232323232323232323232323232323252|-21zGV.5 pjXV.5 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|60e4","Europe/Istanbul|IMT EET EEST TRST TRT|-1U.U -20 -30 -40 -30|012121212121212121212121212121212121212121212121212121234343434342121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ogNU.U dzzU.U 11b0 8tB0 1on0 1410 1db0 19B0 1in0 3Rd0 Un0 1oN0 11b0 zSp0 CL0 mN0 1Vz0 1gN0 1pz0 5Rd0 1fz0 1yp0 ML0 1kp0 17b0 1ip0 17b0 1fB0 19X0 1jB0 18L0 1ip0 17z0 qdd0 xX0 3S10 Tz0 dA10 11z0 1o10 11z0 1qN0 11z0 1ze0 11B0 WM0 1qO0 WI0 1nX0 1rB0 10L0 11B0 1in0 17d0 1in0 2pX0 19E0 1fU0 16Q0 1iI0 16Q0 1iI0 1Vd0 pb0 3Kp0 14o0 1df0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WO0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 Xc0 1qo0 WM0 1qM0 11A0 1o00 1200 1nA0 11A0 1tA0 U00 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|13e6","Asia/Jakarta|BMT JAVT WIB JST WIB WIB|-77.c -7k -7u -90 -80 -70|01232425|-1Q0Tk luM0 mPzO 8vWu 6kpu 4PXu xhcu|31e6","Asia/Jayapura|LMT WIT ACST|-9m.M -90 -9u|0121|-1uu9m.M sMMm.M L4nu|26e4","Asia/Jerusalem|JMT IST IDT IDDT|-2k.E -20 -30 -40|01212121212132121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-26Bek.E SyMk.E 5Rb0 10r0 1px0 10N0 1pz0 16p0 1jB0 16p0 1jx0 3LB0 Em0 or0 1cn0 1dB0 16n0 10O0 1ja0 1tC0 14o0 1cM0 1a00 11A0 1Na0 An0 1MP0 AJ0 1Kp0 LC0 1oo0 Wl0 EQN0 Db0 1fB0 Rb0 npB0 11z0 1C10 IL0 1s10 10n0 1o10 WL0 1zd0 On0 1ld0 11z0 1o10 14n0 1o10 14n0 1nd0 12n0 1nd0 Xz0 1q10 12n0 1hB0 1dX0 1ep0 1aL0 1eN0 17X0 1nf0 11z0 1tB0 19W0 1e10 17b0 1ep0 1gL0 18N0 1fz0 1eN0 17b0 1gq0 1gn0 19d0 1dz0 1c10 17X0 1hB0 1gn0 19d0 1dz0 1c10 17X0 1kp0 1dz0 1c10 1aL0 1eN0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0 10N0 1rz0 W10 1rz0 W10 1rz0 W10 1rz0 10N0 1oL0 10N0 1oL0|81e4","Asia/Kabul|AFT AFT|-40 -4u|01|-10Qs0|46e5","Asia/Kamchatka|LMT PETT PETT PETST PETST|-ay.A -b0 -c0 -d0 -c0|01232323232323232323232412323232323232323232323232323232323232412|-1SLKy.A ivXy.A 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0|18e4","Asia/Karachi|LMT IST IST KART PKT PKST|-4s.c -5u -6u -50 -50 -60|012134545454|-2xoss.c 1qOKW.c 7zX0 eup0 LqMu 1fy00 1cL0 dK10 11b0 1610 1jX0|24e6","Asia/Urumqi|LMT XJT|-5O.k -60|01|-1GgtO.k|32e5","Asia/Kathmandu|LMT IST NPT|-5F.g -5u -5J|012|-21JhF.g 2EGMb.g|12e5","Asia/Khandyga|LMT YAKT YAKT YAKST YAKST VLAT VLAST VLAT YAKT|-92.d -80 -90 -a0 -90 -a0 -b0 -b0 -a0|01232323232323232323232412323232323232323232323232565656565656565782|-21Q92.d pAp2.d 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 qK0 yN0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 17V0 7zD0|66e2","Asia/Krasnoyarsk|LMT KRAT KRAT KRAST KRAST KRAT|-6b.q -60 -70 -80 -70 -80|012323232323232323232324123232323232323232323232323232323232323252|-21Hib.q prAb.q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|10e5","Asia/Kuala_Lumpur|SMT MALT MALST MALT MALT JST MYT|-6T.p -70 -7k -7k -7u -90 -80|01234546|-2Bg6T.p 17anT.p 7hXE dM00 17bO 8Fyu 1so1u|71e5","Asia/Kuching|LMT BORT BORT BORTST JST MYT|-7l.k -7u -80 -8k -90 -80|01232323232323232425|-1KITl.k gDbP.k 6ynu AnE 1O0k AnE 1NAk AnE 1NAk AnE 1NAk AnE 1O0k AnE 1NAk AnE pAk 8Fz0 1so10|13e4","Asia/Macau|LMT MOT MOST CST|-7y.k -80 -90 -80|0121212121212121212121212121212121212121213|-2le7y.k 1XO34.k 1wn0 Rd0 1wn0 R9u 1wqu U10 1tz0 TVu 1tz0 17gu 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cJu 1cL0 1cN0 1fz0 1cN0 1cOu 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cJu 1cL0 1cN0 1fz0 1cN0 1cL0 KEp0|57e4","Asia/Magadan|LMT MAGT MAGT MAGST MAGST MAGT|-a3.c -a0 -b0 -c0 -b0 -c0|0123232323232323232323241232323232323232323232323232323232323232512|-1Pca3.c eUo3.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3Cq0|95e3","Asia/Makassar|LMT MMT WITA JST|-7V.A -7V.A -80 -90|01232|-21JjV.A vfc0 myLV.A 8ML0|15e5","Asia/Manila|PHT PHST JST|-80 -90 -90|010201010|-1kJI0 AL0 cK10 65X0 mXB0 vX0 VK10 1db0|24e6","Asia/Nicosia|LMT EET EEST|-2d.s -20 -30|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1Vc2d.s 2a3cd.s 1cL0 1qp0 Xz0 19B0 19X0 1fB0 1db0 1cp0 1cL0 1fB0 19X0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1o30 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|32e4","Asia/Novokuznetsk|LMT +06 +07 +08|-5M.M -60 -70 -80|012323232323232323232321232323232323232323232323232323232323212|-1PctM.M eULM.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 2sp0 WM0|55e4","Asia/Novosibirsk|LMT +06 +07 +08|-5v.E -60 -70 -80|0123232323232323232323212323212121212121212121212121212121212121212|-21Qnv.E pAFv.E 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 ml0 Os0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 4eN0|15e5","Asia/Omsk|LMT OMST OMST OMSST OMSST OMST|-4R.u -50 -60 -70 -60 -70|012323232323232323232324123232323232323232323232323232323232323252|-224sR.u pMLR.u 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|12e5","Asia/Oral|LMT +04 +05 +06|-3p.o -40 -50 -60|01232323232323232121212121212121212121212121212|-1Pc3p.o eUnp.o 23CL0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 1cM0 1cM0 IM0 1EM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0|27e4","Asia/Pontianak|LMT PMT WIB JST WIB WITA WIB|-7h.k -7h.k -7u -90 -80 -80 -70|012324256|-2ua7h.k XE00 munL.k 8Rau 6kpu 4PXu xhcu Wqnu|23e4","Asia/Pyongyang|LMT KST JCST JST KST|-8n -8u -90 -90 -90|012341|-2um8n 97XR 12FXu jdA0 2Onc0|29e5","Asia/Qyzylorda|LMT +04 +05 +06|-4l.Q -40 -50 -60|0123232323232323232323232323232323232323232323|-1Pc4l.Q eUol.Q 23CL0 3Db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 3ao0 1EM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0|73e4","Asia/Rangoon|RMT BURT JST MMT|-6o.E -6u -90 -6u|0123|-21Jio.E SmnS.E 7j9u|48e5","Asia/Sakhalin|LMT JCST JST SAKT SAKST SAKST SAKT|-9u.M -90 -90 -b0 -c0 -b0 -a0|01234343434343434343434356343434343435656565656565656565656565656363|-2AGVu.M 1iaMu.M je00 1qFa0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o10 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0|58e4","Asia/Samarkand|LMT SAMT SAMT SAMST TAST UZST UZT|-4r.R -40 -50 -60 -60 -60 -50|01234323232323232323232356|-1Pc4r.R eUor.R 23CL0 1db0 1cM0 1dc0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 11x0 bf0|36e4","Asia/Seoul|LMT KST JCST JST KST KDT KDT|-8r.Q -8u -90 -90 -90 -9u -a0|01234151515151515146464|-2um8r.Q 97XV.Q 12FXu jjA0 kKo0 2I0u OL0 1FB0 Rb0 1qN0 TX0 1tB0 TX0 1tB0 TX0 1tB0 TX0 2ap0 12FBu 11A0 1o00 11A0|23e6","Asia/Singapore|SMT MALT MALST MALT MALT JST SGT SGT|-6T.p -70 -7k -7k -7u -90 -7u -80|012345467|-2Bg6T.p 17anT.p 7hXE dM00 17bO 8Fyu Mspu DTA0|56e5","Asia/Srednekolymsk|LMT MAGT MAGT MAGST MAGST MAGT SRET|-ae.Q -a0 -b0 -c0 -b0 -c0 -b0|012323232323232323232324123232323232323232323232323232323232323256|-1Pcae.Q eUoe.Q 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|35e2","Asia/Taipei|JWST JST CST CDT|-80 -90 -80 -90|01232323232323232323232323232323232323232|-1iw80 joM0 1yo0 Tz0 1ip0 1jX0 1cN0 11b0 1oN0 11b0 1oN0 11b0 1oN0 11b0 10N0 1BX0 10p0 1pz0 10p0 1pz0 10p0 1db0 1dd0 1db0 1cN0 1db0 1cN0 1db0 1cN0 1db0 1BB0 ML0 1Bd0 ML0 uq10 1db0 1cN0 1db0 97B0 AL0|74e5","Asia/Tashkent|LMT TAST TAST TASST TASST UZST UZT|-4B.b -50 -60 -70 -60 -60 -50|01232323232323232323232456|-1Pc4B.b eUnB.b 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 11y0 bf0|23e5","Asia/Tbilisi|TBMT TBIT TBIT TBIST TBIST GEST GET GET GEST|-2X.b -30 -40 -50 -40 -40 -30 -40 -50|0123232323232323232323245656565787878787878787878567|-1Pc2X.b 1jUnX.b WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 3y0 19f0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cM0 1cL0 1fB0 3Nz0 11B0 1nX0 11B0 1qL0 WN0 1qL0 WN0 1qL0 11B0 1nX0 11B0 1nX0 11B0 An0 Os0 WM0|11e5","Asia/Tehran|LMT TMT IRST IRST IRDT IRDT|-3p.I -3p.I -3u -40 -50 -4u|01234325252525252525252525252525252525252525252525252525252525252525252525252525252525252525252525252|-2btDp.I 1d3c0 1huLT.I TXu 1pz0 sN0 vAu 1cL0 1dB0 1en0 pNB0 UL0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 64p0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0 1cN0 1dz0 1cp0 1dz0 1cp0 1dz0 1cp0 1dz0|14e6","Asia/Thimphu|LMT IST BTT|-5W.A -5u -60|012|-Su5W.A 1BGMs.A|79e3","Asia/Tokyo|JCST JST JDT|-90 -90 -a0|0121212121|-1iw90 pKq0 QL0 1lB0 13X0 1zB0 NX0 1zB0 NX0|38e6","Asia/Tomsk|LMT +06 +07 +08|-5D.P -60 -70 -80|0123232323232323232323212323232323232323232323212121212121212121212|-21NhD.P pxzD.P 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 co0 1bB0 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3Qp0|10e5","Asia/Ulaanbaatar|LMT ULAT ULAT ULAST|-77.w -70 -80 -90|012323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-2APH7.w 2Uko7.w cKn0 1db0 1dd0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 6hD0 11z0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 kEp0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1fx0 1cP0 1cJ0 1cP0 1cJ0 1cP0 1cJ0|12e5","Asia/Ust-Nera|LMT YAKT YAKT MAGST MAGT MAGST MAGT MAGT VLAT VLAT|-9w.S -80 -90 -c0 -b0 -b0 -a0 -c0 -b0 -a0|0123434343434343434343456434343434343434343434343434343434343434789|-21Q9w.S pApw.S 23CL0 1d90 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 17V0 7zD0|65e2","Asia/Vladivostok|LMT VLAT VLAT VLAST VLAST VLAT|-8L.v -90 -a0 -b0 -a0 -b0|012323232323232323232324123232323232323232323232323232323232323252|-1SJIL.v itXL.v 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|60e4","Asia/Yakutsk|LMT YAKT YAKT YAKST YAKST YAKT|-8C.W -80 -90 -a0 -90 -a0|012323232323232323232324123232323232323232323232323232323232323252|-21Q8C.W pAoC.W 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|28e4","Asia/Yekaterinburg|LMT PMT SVET SVET SVEST SVEST YEKT YEKST YEKT|-42.x -3J.5 -40 -50 -60 -50 -50 -60 -60|0123434343434343434343435267676767676767676767676767676767676767686|-2ag42.x 7mQh.s qBvJ.5 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|14e5","Asia/Yerevan|LMT YERT YERT YERST YERST AMST AMT AMT AMST|-2W -30 -40 -50 -40 -40 -30 -40 -50|0123232323232323232323245656565657878787878787878787878787878787|-1Pc2W 1jUnW WCL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1am0 2r0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 3Fb0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0|13e5","Atlantic/Azores|HMT AZOT AZOST AZOMT AZOT AZOST WET|1S.w 20 10 0 10 0 0|01212121212121212121212121212121212121212121232123212321232121212121212121212121212121212121212121454545454545454545454545454545456545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2ldW5.s aPX5.s Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 qIl0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cL0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|25e4","Atlantic/Bermuda|LMT AST ADT|4j.i 40 30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1BnRE.G 1LTbE.G 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0|65e3","Atlantic/Canary|LMT CANT WET WEST|11.A 10 0 -10|01232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-1UtaW.o XPAW.o 1lAK0 1a10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|54e4","Atlantic/Cape_Verde|LMT CVT CVST CVT|1y.4 20 10 10|01213|-2xomp.U 1qOMp.U 7zX0 1djf0|50e4","Atlantic/Faroe|LMT WET WEST|r.4 0 -10|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2uSnw.U 2Wgow.U 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|49e3","Atlantic/Madeira|FMT MADT MADST MADMT WET WEST|17.A 10 0 -10 0 -10|01212121212121212121212121212121212121212121232123212321232121212121212121212121212121212121212121454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2ldWQ.o aPWQ.o Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 qIl0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|27e4","Atlantic/Reykjavik|LMT IST ISST GMT|1s 10 0 0|012121212121212121212121212121212121212121212121212121212121212121213|-2uWmw mfaw 1Bd0 ML0 1LB0 Cn0 1LB0 3fX0 C10 HrX0 1cO0 LB0 1EL0 LA0 1C00 Oo0 1wo0 Rc0 1wo0 Rc0 1wo0 Rc0 1zc0 Oo0 1zc0 14o0 1lc0 14o0 1lc0 14o0 1o00 11A0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1lc0 14o0 1o00 14o0|12e4","Atlantic/South_Georgia|GST|20|0||30","Atlantic/Stanley|SMT FKT FKST FKT FKST|3P.o 40 30 30 20|0121212121212134343212121212121212121212121212121212121212121212121212|-2kJw8.A 12bA8.A 19X0 1fB0 19X0 1ip0 19X0 1fB0 19X0 1fB0 19X0 1fB0 Cn0 1Cc10 WL0 1qL0 U10 1tz0 U10 1qM0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1tz0 U10 1tz0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1tz0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qL0 WN0 1qN0 U10 1wn0 Rd0 1wn0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1tz0 U10 1wn0 U10 1tz0 U10 1tz0 U10|21e2","Australia/Sydney|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 14o0 1o00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 11A0 1o00 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|40e5","Australia/Adelaide|ACST ACDT|-9u -au|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 Oo0 1zc0 WM0 1qM0 Rc0 1zc0 U00 1tA0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|11e5","Australia/Brisbane|AEST AEDT|-a0 -b0|01010101010101010|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 H1A0 Oo0 1zc0 Oo0 1zc0 Oo0|20e5","Australia/Broken_Hill|ACST ACDT|-9u -au|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 14o0 1o00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1tA0 WM0 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|18e3","Australia/Currie|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-29E80 19X0 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|746","Australia/Darwin|ACST ACDT|-9u -au|010101010|-293lt xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0|12e4","Australia/Eucla|ACWST ACWDT|-8J -9J|0101010101010101010|-293kI xcX 10jd0 yL0 1cN0 1cL0 1gSp0 Oo0 l5A0 Oo0 iJA0 G00 zU00 IM0 1qM0 11A0 1o00 11A0|368","Australia/Hobart|AEST AEDT|-a0 -b0|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-29E80 19X0 10jd0 yL0 1cN0 1cL0 1fB0 19X0 VfB0 1cM0 1o00 Rc0 1wo0 Rc0 1wo0 U00 1wo0 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 11A0 1qM0 WM0 1qM0 Oo0 1zc0 Oo0 1zc0 Oo0 1wo0 WM0 1tA0 WM0 1tA0 U00 1tA0 U00 1tA0 11A0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 11A0 1o00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|21e4","Australia/Lord_Howe|AEST LHST LHDT LHDT|-a0 -au -bu -b0|0121212121313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313|raC0 1zdu Rb0 1zd0 On0 1zd0 On0 1zd0 On0 1zd0 TXu 1qMu WLu 1tAu WLu 1tAu TXu 1tAu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu 11zu 1o0u 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 11Au 1nXu 1qMu 11zu 1o0u 11zu 1o0u 11zu 1qMu WLu 1qMu 11zu 1o0u WLu 1qMu 14nu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1fzu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1cMu 1cLu 1fAu 1cLu 1cMu 1cLu 1cMu|347","Australia/Lindeman|AEST AEDT|-a0 -b0|010101010101010101010|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 H1A0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0|10","Australia/Melbourne|AEST AEDT|-a0 -b0|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101|-293lX xcX 10jd0 yL0 1cN0 1cL0 1fB0 19X0 17c10 LA0 1C00 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 U00 1qM0 WM0 1qM0 11A0 1tA0 U00 1tA0 U00 1tA0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 11A0 1o00 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 14o0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0|39e5","Australia/Perth|AWST AWDT|-80 -90|0101010101010101010|-293jX xcX 10jd0 yL0 1cN0 1cL0 1gSp0 Oo0 l5A0 Oo0 iJA0 G00 zU00 IM0 1qM0 11A0 1o00 11A0|18e5","CET|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00","CST6CDT|CST CDT CWT CPT|60 50 50 50|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261s0 1nX0 11B0 1nX0 SgN0 8x30 iw0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","Pacific/Easter|EMT EAST EASST EAST EASST|7h.s 70 60 60 50|0121212121212121212121212121234343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-1uSgG.w 1s4IG.w WL0 1zd0 On0 1ip0 11z0 1o10 11z0 1qN0 WL0 1ld0 14n0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 WL0 1qN0 1cL0 1cN0 11z0 1o10 11z0 1qN0 WL0 1fB0 19X0 1qN0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1ip0 1fz0 1fB0 11z0 1qN0 WL0 1qN0 WL0 1qN0 WL0 1qN0 11z0 1o10 11z0 1o10 11z0 1qN0 WL0 1qN0 17b0 1ip0 11z0 1o10 19X0 1fB0 1nX0 G10 1EL0 Op0 1zb0 Rd0 1wn0 Rd0 46n0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Dd0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0 1Nb0 Ap0|30e2","EET|EET EEST|-20 -30|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|hDB0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00","EST|EST|50|0|","EST5EDT|EST EDT EWT EPT|50 40 40 40|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261t0 1nX0 11B0 1nX0 SgN0 8x40 iv0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","Europe/Dublin|DMT IST GMT BST IST|p.l -y.D 0 -10 -10|01232323232324242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242|-2ax9y.D Rc0 1fzy.D 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 g5X0 14p0 1wn0 17d0 1io0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1a00 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1tA0 IM0 90o0 U00 1tA0 U00 1tA0 U00 1tA0 U00 1tA0 WM0 1qM0 WM0 1qM0 WM0 1tA0 U00 1tA0 U00 1tA0 11z0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 14o0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5","Etc/GMT+0|GMT|0|0|","Etc/GMT+1|GMT+1|10|0|","Etc/GMT+10|GMT+10|a0|0|","Etc/GMT+11|GMT+11|b0|0|","Etc/GMT+12|GMT+12|c0|0|","Etc/GMT+2|GMT+2|20|0|","Etc/GMT+3|GMT+3|30|0|","Etc/GMT+4|GMT+4|40|0|","Etc/GMT+5|GMT+5|50|0|","Etc/GMT+6|GMT+6|60|0|","Etc/GMT+7|GMT+7|70|0|","Etc/GMT+8|GMT+8|80|0|","Etc/GMT+9|GMT+9|90|0|","Etc/GMT-1|GMT-1|-10|0|","Etc/GMT-10|GMT-10|-a0|0|","Etc/GMT-11|GMT-11|-b0|0|","Etc/GMT-12|GMT-12|-c0|0|","Etc/GMT-13|GMT-13|-d0|0|","Etc/GMT-14|GMT-14|-e0|0|","Etc/GMT-2|GMT-2|-20|0|","Etc/GMT-3|GMT-3|-30|0|","Etc/GMT-4|GMT-4|-40|0|","Etc/GMT-5|GMT-5|-50|0|","Etc/GMT-6|GMT-6|-60|0|","Etc/GMT-7|GMT-7|-70|0|","Etc/GMT-8|GMT-8|-80|0|","Etc/GMT-9|GMT-9|-90|0|","Etc/UCT|UCT|0|0|","Etc/UTC|UTC|0|0|","Europe/Amsterdam|AMT NST NEST NET CEST CET|-j.w -1j.w -1k -k -20 -10|010101010101010101010101010101010101010101012323234545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545|-2aFcj.w 11b0 1iP0 11A0 1io0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1co0 1io0 1yo0 Pc0 1a00 1fA0 1Bc0 Mo0 1tc0 Uo0 1tA0 U00 1uo0 W00 1s00 VA0 1so0 Vc0 1sM0 UM0 1wo0 Rc0 1u00 Wo0 1rA0 W00 1s00 VA0 1sM0 UM0 1w00 fV0 BCX.w 1tA0 U00 1u00 Wo0 1sm0 601k WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|16e5","Europe/Andorra|WET CET CEST|0 -10 -20|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-UBA0 1xIN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|79e3","Europe/Astrakhan|LMT +03 +04 +05|-3c.c -30 -40 -50|012323232323232323212121212121212121212121212121212121212121212|-1Pcrc.c eUMc.c 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 1cM0 3Co0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0","Europe/Athens|AMT EET EEST CEST CET|-1y.Q -20 -30 -20 -10|012123434121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2a61x.Q CNbx.Q mn0 kU10 9b0 3Es0 Xa0 1fb0 1dd0 k3X0 Nz0 SCp0 1vc0 SO0 1cM0 1a00 1ao0 1fc0 1a10 1fG0 1cg0 1dX0 1bX0 1cQ0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|35e5","Europe/London|GMT BST BDST|0 -10 -20|0101010101010101010101010101010101010101010101010121212121210101210101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2axa0 Rc0 1fA0 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 2Rz0 Dc0 1zc0 Oo0 1zc0 Rc0 1wo0 17c0 1iM0 FA0 xB0 1fA0 1a00 14o0 bb0 LA0 xB0 Rc0 1wo0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1a00 1qM0 WM0 1qM0 11A0 1o00 WM0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1tA0 IM0 90o0 U00 1tA0 U00 1tA0 U00 1tA0 U00 1tA0 WM0 1qM0 WM0 1qM0 WM0 1tA0 U00 1tA0 U00 1tA0 11z0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1o00 14o0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|10e6","Europe/Belgrade|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19RC0 3IP0 WM0 1fA0 1cM0 1cM0 1rc0 Qo0 1vmo0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5","Europe/Berlin|CET CEST CEMT|-10 -20 -30|01010101010101210101210101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 kL0 Nc0 m10 WM0 1ao0 1cp0 dX0 jz0 Dd0 1io0 17c0 1fA0 1a00 1ehA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|41e5","Europe/Prague|CET CEST|-10 -20|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 16M0 1lc0 1tA0 17A0 11c0 1io0 17c0 1io0 17c0 1fc0 1ao0 1bNc0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|13e5","Europe/Brussels|WET CET CEST WEST|0 -10 -20 -10|0121212103030303030303030303030303030303030303030303212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ehc0 3zX0 11c0 1iO0 11A0 1o00 11A0 my0 Ic0 1qM0 Rc0 1EM0 UM0 1u00 10o0 1io0 1io0 17c0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a30 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 y00 5Wn0 WM0 1fA0 1cM0 16M0 1iM0 16M0 1C00 Uo0 1eeo0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|21e5","Europe/Bucharest|BMT EET EEST|-1I.o -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1xApI.o 20LI.o RA0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Axc0 On0 1fA0 1a10 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cK0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|19e5","Europe/Budapest|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1ip0 17b0 1op0 1tb0 Q2m0 3Ne0 WM0 1fA0 1cM0 1cM0 1oJ0 1dc0 1030 1fA0 1cM0 1cM0 1cM0 1cM0 1fA0 1a00 1iM0 1fA0 8Ha0 Rb0 1wN0 Rb0 1BB0 Lz0 1C20 LB0 SNX0 1a10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e5","Europe/Zurich|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-19Lc0 11A0 1o00 11A0 1xG10 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|38e4","Europe/Chisinau|CMT BMT EET EEST CEST CET MSK MSD|-1T -1I.o -20 -30 -20 -10 -30 -40|012323232323232323234545467676767676767676767323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232|-26jdT wGMa.A 20LI.o RA0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 27A0 2en0 39g0 WM0 1fA0 1cM0 V90 1t7z0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 gL0 WO0 1cM0 1cM0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1nX0 11D0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|67e4","Europe/Copenhagen|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2azC0 Tz0 VuO0 60q0 WM0 1fA0 1cM0 1cM0 1cM0 S00 1HA0 Nc0 1C00 Dc0 1Nc0 Ao0 1h5A0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5","Europe/Gibraltar|GMT BST BDST CET CEST|0 -10 -20 -10 -20|010101010101010101010101010101010101010101010101012121212121010121010101010101010101034343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-2axa0 Rc0 1fA0 14M0 1fc0 1g00 1co0 1dc0 1co0 1oo0 1400 1dc0 19A0 1io0 1io0 WM0 1o00 14o0 1o00 17c0 1io0 17c0 1fA0 1a00 1lc0 17c0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1cM0 1io0 17c0 1fA0 1a00 1io0 17c0 1io0 17c0 1fA0 1a00 1io0 1qM0 Dc0 2Rz0 Dc0 1zc0 Oo0 1zc0 Rc0 1wo0 17c0 1iM0 FA0 xB0 1fA0 1a00 14o0 bb0 LA0 xB0 Rc0 1wo0 11A0 1o00 17c0 1fA0 1a00 1fA0 1cM0 1fA0 1a00 17c0 1fA0 1a00 1io0 17c0 1lc0 17c0 1fA0 10Jz0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|30e3","Europe/Helsinki|HMT EET EEST|-1D.N -20 -30|0121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-1WuND.N OULD.N 1dA0 1xGq0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5","Europe/Kaliningrad|CET CEST CET CEST MSK MSD EEST EET FET|-10 -20 -20 -30 -30 -40 -30 -20 -30|0101010101010232454545454545454546767676767676767676767676767676767676767676787|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 Am0 Lb0 1en0 op0 1pNz0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|44e4","Europe/Kiev|KMT EET MSK CEST CET MSD EEST|-22.4 -20 -30 -20 -10 -40 -30|0123434252525252525252525256161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161|-1Pc22.4 eUo2.4 rnz0 2Hg0 WM0 1fA0 da0 1v4m0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 Db0 3220 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|34e5","Europe/Kirov|LMT +03 +04 +05|-3i.M -30 -40 -50|01232323232323232321212121212121212121212121212121212121212121|-22WNi.M qHai.M 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 1cM0 3Co0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|48e4","Europe/Lisbon|LMT WET WEST WEMT CET CEST|A.J 0 -10 -20 -10 -20|012121212121212121212121212121212121212121212321232123212321212121212121212121212121212121212121214121212121212121212121212121212124545454212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ldXn.f aPWn.f Sp0 LX0 1vc0 Tc0 1uM0 SM0 1vc0 Tc0 1vc0 SM0 1vc0 6600 1co0 3E00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 3I00 17c0 1cM0 1cM0 3Fc0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 1tA0 1cM0 1dc0 1400 gL0 IM0 s10 U00 dX0 Rc0 pd0 Rc0 gL0 Oo0 pd0 Rc0 gL0 Oo0 pd0 14o0 1cM0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 3Co0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 pvy0 1cM0 1cM0 1fA0 1cM0 1cM0 1cN0 1cL0 1cN0 1cM0 1cM0 1cM0 1cM0 1cN0 1cL0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|27e5","Europe/Luxembourg|LMT CET CEST WET WEST WEST WET|-o.A -10 -20 0 -10 -20 -10|0121212134343434343434343434343434343434343434343434565651212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2DG0o.A t6mo.A TB0 1nX0 Up0 1o20 11A0 rW0 CM0 1qP0 R90 1EO0 UK0 1u20 10m0 1ip0 1in0 17e0 19W0 1fB0 1db0 1cp0 1in0 17d0 1fz0 1a10 1in0 1a10 1in0 17f0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Dc0 vA0 60L0 WM0 1fA0 1cM0 17c0 1io0 16M0 1C00 Uo0 1eeo0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|54e4","Europe/Madrid|WET WEST WEMT CET CEST|0 -10 -20 -10 -20|01010101010101010101010121212121234343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343|-28dd0 11A0 1go0 19A0 1co0 1dA0 b1A0 18o0 3I00 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 iyo0 Rc0 18o0 1hc0 1io0 1a00 14o0 5aL0 MM0 1vc0 17A0 1i00 1bc0 1eo0 17d0 1in0 17A0 6hA0 10N0 XIL0 1a10 1in0 17d0 19X0 1cN0 1fz0 1a10 1fX0 1cp0 1cO0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|62e5","Europe/Malta|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2as10 M00 1cM0 1cM0 14o0 1o00 WM0 1qM0 17c0 1cM0 M3A0 5M20 WM0 1fA0 1cM0 1cM0 1cM0 16m0 1de0 1lc0 14m0 1lc0 WO0 1qM0 GTW0 On0 1C10 Lz0 1C10 Lz0 1EN0 Lz0 1C10 Lz0 1zd0 Oo0 1C00 On0 1cp0 1cM0 1lA0 Xc0 1qq0 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1o10 11z0 1iN0 19z0 1fB0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|42e4","Europe/Minsk|MMT EET MSK CEST CET MSD EEST FET|-1O -20 -30 -20 -10 -40 -30 -30|012343432525252525252525252616161616161616161616161616161616161616172|-1Pc1O eUnO qNX0 3gQ0 WM0 1fA0 1cM0 Al0 1tsn0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 3Fc0 1cN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hy0|19e5","Europe/Monaco|PMT WET WEST WEMT CET CEST|-9.l 0 -10 -20 -10 -20|01212121212121212121212121212121212121212121212121232323232345454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-2nco9.l cNb9.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 1u00 10o0 1io0 1wo0 Rc0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Df0 2RV0 11z0 11B0 1ze0 WM0 1fA0 1cM0 1fa0 1aq0 16M0 1ekn0 1cL0 1fC0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|38e3","Europe/Moscow|MMT MMT MST MDST MSD MSK MSM EET EEST MSK|-2u.h -2v.j -3v.j -4v.j -40 -30 -50 -20 -30 -40|012132345464575454545454545454545458754545454545454545454545454545454545454595|-2ag2u.h 2pyW.W 1bA0 11X0 GN0 1Hb0 c20 imv.j 3DA0 dz0 15A0 c10 2q10 iM10 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|16e6","Europe/Paris|PMT WET WEST CEST CET WEMT|-9.l 0 -10 -20 -10 -20|0121212121212121212121212121212121212121212121212123434352543434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434343434|-2nco8.l cNb8.l HA0 19A0 1iM0 11c0 1oo0 Wo0 1rc0 QM0 1EM0 UM0 1u00 10o0 1io0 1wo0 Rc0 1a00 1fA0 1cM0 1cM0 1io0 17c0 1fA0 1a00 1io0 1a00 1io0 17c0 1fA0 1a00 1io0 17c0 1cM0 1cM0 1a00 1io0 1cM0 1cM0 1a00 1fA0 1io0 17c0 1cM0 1cM0 1a00 1fA0 1io0 1qM0 Df0 Ik0 5M30 WM0 1fA0 1cM0 Vx0 hB0 1aq0 16M0 1ekn0 1cL0 1fC0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|11e6","Europe/Riga|RMT LST EET MSK CEST CET MSD EEST|-1A.y -2A.y -20 -30 -20 -10 -40 -30|010102345454536363636363636363727272727272727272727272727272727272727272727272727272727272727272727272727272727272727272727272|-25TzA.y 11A0 1iM0 ko0 gWm0 yDXA.y 2bX0 3fE0 WM0 1fA0 1cM0 1cM0 4m0 1sLy0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cN0 1o00 11A0 1o00 11A0 1qM0 3oo0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|64e4","Europe/Rome|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2as10 M00 1cM0 1cM0 14o0 1o00 WM0 1qM0 17c0 1cM0 M3A0 5M20 WM0 1fA0 1cM0 16K0 1iO0 16m0 1de0 1lc0 14m0 1lc0 WO0 1qM0 GTW0 On0 1C10 Lz0 1C10 Lz0 1EN0 Lz0 1C10 Lz0 1zd0 Oo0 1C00 On0 1C10 Lz0 1zd0 On0 1C10 LA0 1C00 LA0 1zc0 Oo0 1C00 Oo0 1zc0 Oo0 1fC0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|39e5","Europe/Samara|LMT SAMT SAMT KUYT KUYST MSD MSK EEST SAMST SAMST|-3k.k -30 -40 -40 -50 -40 -30 -30 -50 -40|012343434343434343435656712828282828282828282828282828282828282912|-22WNk.k qHak.k bcn0 1Qqo0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cN0 8o0 14m0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qN0 WM0|12e5","Europe/Simferopol|SMT EET MSK CEST CET MSD EEST MSK|-2g -20 -30 -20 -10 -40 -30 -40|012343432525252525252525252161616525252616161616161616161616161616161616172|-1Pc2g eUog rEn0 2qs0 WM0 1fA0 1cM0 3V0 1u0L0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Q00 4eL0 1cL0 1cN0 1cL0 1cN0 dX0 WL0 1cN0 1cL0 1fB0 1o30 11B0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11z0 1nW0|33e4","Europe/Sofia|EET CET CEST EEST|-20 -10 -20 -30|01212103030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030|-168L0 WM0 1fA0 1cM0 1cM0 1cN0 1mKH0 1dd0 1fb0 1ap0 1fb0 1a20 1fy0 1a30 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cK0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 1nX0 11E0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|12e5","Europe/Stockholm|CET CEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2azC0 TB0 2yDe0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|15e5","Europe/Tallinn|TMT CET CEST EET MSK MSD EEST|-1D -10 -20 -20 -30 -40 -30|012103421212454545454545454546363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363|-26oND teD 11A0 1Ta0 4rXl KSLD 2FX0 2Jg0 WM0 1fA0 1cM0 18J0 1sTX0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o10 11A0 1qM0 5QM0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|41e4","Europe/Tirane|LMT CET CEST|-1j.k -10 -20|01212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2glBj.k 14pcj.k 5LC0 WM0 4M0 1fCK0 10n0 1op0 11z0 1pd0 11z0 1qN0 WL0 1qp0 Xb0 1qp0 Xb0 1qp0 11z0 1lB0 11z0 1qN0 11z0 1iN0 16n0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|42e4","Europe/Ulyanovsk|LMT +03 +04 +05 +02|-3d.A -30 -40 -50 -20|01232323232323232321214121212121212121212121212121212121212121212|-22WNd.A qHad.A 23CL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 2pB0 1cM0 1fA0 2pB0 IM0 rX0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0 3rd0","Europe/Uzhgorod|CET CEST MSK MSD EET EEST|-10 -20 -30 -40 -20 -30|010101023232323232323232320454545454545454545454545454545454545454545454545454545454545454545454545454545454545454545454|-1cqL0 6i00 WM0 1fA0 1cM0 1ml0 1Cp0 1r3W0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1Q00 1Nf0 2pw0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|11e4","Europe/Vienna|CET CEST|-10 -20|0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 3KM0 14o0 LA00 6i00 WM0 1fA0 1cM0 1cM0 1cM0 400 2qM0 1a00 1cM0 1cM0 1io0 17c0 1gHa0 19X0 1cP0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|18e5","Europe/Vilnius|WMT KMT CET EET MSK CEST MSD EEST|-1o -1z.A -10 -20 -30 -20 -40 -30|012324525254646464646464646473737373737373737352537373737373737373737373737373737373737373737373737373737373737373737373|-293do 6ILM.o 1Ooz.A zz0 Mfd0 29W0 3is0 WM0 1fA0 1cM0 LV0 1tgL0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11B0 1o00 11A0 1qM0 8io0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|54e4","Europe/Volgograd|LMT TSAT STAT STAT VOLT VOLST VOLST VOLT MSD MSK MSK|-2V.E -30 -30 -40 -40 -50 -40 -30 -40 -30 -40|0123454545454545454676767489898989898989898989898989898989898989a9|-21IqV.E cLXV.E cEM0 1gqn0 Lco0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1cM0 1fA0 1cM0 2pz0 1cN0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 8Hz0|10e5","Europe/Warsaw|WMT CET CEST EET EEST|-1o -10 -20 -20 -30|012121234312121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121|-2ctdo 1LXo 11d0 1iO0 11A0 1o00 11A0 1on0 11A0 6zy0 HWP0 5IM0 WM0 1fA0 1cM0 1dz0 1mL0 1en0 15B0 1aq0 1nA0 11A0 1io0 17c0 1fA0 1a00 iDX0 LA0 1cM0 1cM0 1C00 Oo0 1cM0 1cM0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1C00 LA0 uso0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cN0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|17e5","Europe/Zaporozhye|CUT EET MSK CEST CET MSD EEST|-2k -20 -30 -20 -10 -40 -30|01234342525252525252525252526161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161|-1Pc2k eUok rdb0 2RE0 WM0 1fA0 8m0 1v9a0 1db0 1cN0 1db0 1cN0 1db0 1dd0 1cO0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cK0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cQ0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00|77e4","HST|HST|a0|0|","Indian/Chagos|LMT IOT IOT|-4N.E -50 -60|012|-2xosN.E 3AGLN.E|30e2","Indian/Christmas|CXT|-70|0||21e2","Indian/Cocos|CCT|-6u|0||596","Indian/Kerguelen|-00 TFT|0 -50|01|-MG00|130","Indian/Mahe|LMT SCT|-3F.M -40|01|-2yO3F.M|79e3","Indian/Maldives|MMT MVT|-4S -50|01|-olgS|35e4","Indian/Mauritius|LMT MUT MUST|-3O -40 -50|012121|-2xorO 34unO 14L0 12kr0 11z0|15e4","Indian/Reunion|LMT RET|-3F.Q -40|01|-2mDDF.Q|84e4","Pacific/Kwajalein|MHT KWAT MHT|-b0 c0 -c0|012|-AX0 W9X0|14e3","MET|MET MEST|-10 -20|01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-2aFe0 11d0 1iO0 11A0 1o00 11A0 Qrc0 6i00 WM0 1fA0 1cM0 1cM0 1cM0 16M0 1gMM0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00","MST|MST|70|0|","MST7MDT|MST MDT MWT MPT|70 60 60 60|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261r0 1nX0 11B0 1nX0 SgN0 8x20 ix0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","Pacific/Chatham|CHAST CHAST CHADT|-cf -cJ -dJ|012121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212121212|-WqAf 1adef IM0 1C00 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Oo0 1zc0 Rc0 1zc0 Oo0 1qM0 14o0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1lc0 14o0 1lc0 14o0 1lc0 17c0 1io0 17c0 1io0 17c0 1io0 17c0 1io0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|600","PST8PDT|PST PDT PWT PPT|80 70 70 70|010102301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|-261q0 1nX0 11B0 1nX0 SgN0 8x10 iy0 QwN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1cN0 1cL0 1cN0 1cL0 s10 1Vz0 LB0 1BX0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1fz0 1a10 1fz0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 14p0 1lb0 14p0 1lb0 14p0 1nX0 11B0 1nX0 11B0 1nX0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Rd0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0 Op0 1zb0","Pacific/Apia|LMT WSST SST SDT WSDT WSST|bq.U bu b0 a0 -e0 -d0|01232345454545454545454545454545454545454545454545454545454|-2nDMx.4 1yW03.4 2rRbu 1ff0 1a00 CI0 AQ0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1io0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1a00 1fA0 1cM0 1fA0 1a00 1fA0 1a00|37e3","Pacific/Bougainville|PGT JST BST|-a0 -90 -b0|0102|-16Wy0 7CN0 2MQp0|18e4","Pacific/Chuuk|CHUT|-a0|0||49e3","Pacific/Efate|LMT VUT VUST|-bd.g -b0 -c0|0121212121212121212121|-2l9nd.g 2Szcd.g 1cL0 1oN0 10L0 1fB0 19X0 1fB0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1cN0 1cL0 1fB0 Lz0 1Nd0 An0|66e3","Pacific/Enderbury|PHOT PHOT PHOT|c0 b0 -d0|012|nIc0 B8n0|1","Pacific/Fakaofo|TKT TKT|b0 -d0|01|1Gfn0|483","Pacific/Fiji|LMT FJT FJST|-bT.I -c0 -d0|0121212121212121212121212121212121212121212121212121212121212121|-2bUzT.I 3m8NT.I LA0 1EM0 IM0 nJc0 LA0 1o00 Rc0 1wo0 Ao0 1Nc0 Ao0 1Q00 xz0 1SN0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0 1VA0 s00 1VA0 s00 1VA0 uM0 1SM0 uM0 1SM0 uM0 1SM0 uM0|88e4","Pacific/Funafuti|TVT|-c0|0||45e2","Pacific/Galapagos|LMT ECT GALT|5W.o 50 60|012|-1yVS1.A 2dTz1.A|25e3","Pacific/Gambier|LMT GAMT|8X.M 90|01|-2jof0.c|125","Pacific/Guadalcanal|LMT SBT|-aD.M -b0|01|-2joyD.M|11e4","Pacific/Guam|GST ChST|-a0 -a0|01|1fpq0|17e4","Pacific/Honolulu|HST HDT HST|au 9u a0|010102|-1thLu 8x0 lef0 8Pz0 46p0|37e4","Pacific/Kiritimati|LINT LINT LINT|aE a0 -e0|012|nIaE B8nk|51e2","Pacific/Kosrae|KOST KOST|-b0 -c0|010|-AX0 1bdz0|66e2","Pacific/Majuro|MHT MHT|-b0 -c0|01|-AX0|28e3","Pacific/Marquesas|LMT MART|9i 9u|01|-2joeG|86e2","Pacific/Pago_Pago|LMT NST BST SST|bm.M b0 b0 b0|0123|-2nDMB.c 2gVzB.c EyM0|37e2","Pacific/Nauru|LMT NRT JST NRT|-b7.E -bu -90 -c0|01213|-1Xdn7.E PvzB.E 5RCu 1ouJu|10e3","Pacific/Niue|NUT NUT NUT|bk bu b0|012|-KfME 17y0a|12e2","Pacific/Norfolk|NMT NFT NFST NFT|-bc -bu -cu -b0|01213|-Kgbc W01G On0 1COp0|25e4","Pacific/Noumea|LMT NCT NCST|-b5.M -b0 -c0|01212121|-2l9n5.M 2EqM5.M xX0 1PB0 yn0 HeP0 Ao0|98e3","Pacific/Palau|PWT|-90|0||21e3","Pacific/Pitcairn|PNT PST|8u 80|01|18Vku|56","Pacific/Pohnpei|PONT|-b0|0||34e3","Pacific/Port_Moresby|PGT|-a0|0||25e4","Pacific/Rarotonga|CKT CKHST CKT|au 9u a0|012121212121212121212121212|lyWu IL0 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Onu 1zcu Rbu 1zcu Onu 1zcu Onu 1zcu Onu|13e3","Pacific/Tahiti|LMT TAHT|9W.g a0|01|-2joe1.I|18e4","Pacific/Tarawa|GILT|-c0|0||29e3","Pacific/Tongatapu|TOT TOT TOST|-ck -d0 -e0|01212121|-1aB0k 2n5dk 15A0 1wo0 xz0 1Q10 xz0|75e3","Pacific/Wake|WAKT|-c0|0||16e3","Pacific/Wallis|WFT|-c0|0||94","WET|WET WEST|0 -10|010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010|hDB0 1a00 1fA0 1cM0 1cM0 1cM0 1fA0 1a00 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1cM0 1fA0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00 11A0 1qM0 WM0 1qM0 WM0 1qM0 WM0 1qM0 11A0 1o00 11A0 1o00"], + links:["Africa/Abidjan|Africa/Bamako","Africa/Abidjan|Africa/Banjul","Africa/Abidjan|Africa/Conakry","Africa/Abidjan|Africa/Dakar","Africa/Abidjan|Africa/Freetown","Africa/Abidjan|Africa/Lome","Africa/Abidjan|Africa/Nouakchott","Africa/Abidjan|Africa/Ouagadougou","Africa/Abidjan|Africa/Sao_Tome","Africa/Abidjan|Africa/Timbuktu","Africa/Abidjan|Atlantic/St_Helena","Africa/Cairo|Egypt","Africa/Johannesburg|Africa/Maseru","Africa/Johannesburg|Africa/Mbabane","Africa/Khartoum|Africa/Juba","Africa/Lagos|Africa/Bangui","Africa/Lagos|Africa/Brazzaville","Africa/Lagos|Africa/Douala","Africa/Lagos|Africa/Kinshasa","Africa/Lagos|Africa/Libreville","Africa/Lagos|Africa/Luanda","Africa/Lagos|Africa/Malabo","Africa/Lagos|Africa/Niamey","Africa/Lagos|Africa/Porto-Novo","Africa/Maputo|Africa/Blantyre","Africa/Maputo|Africa/Bujumbura","Africa/Maputo|Africa/Gaborone","Africa/Maputo|Africa/Harare","Africa/Maputo|Africa/Kigali","Africa/Maputo|Africa/Lubumbashi","Africa/Maputo|Africa/Lusaka","Africa/Nairobi|Africa/Addis_Ababa","Africa/Nairobi|Africa/Asmara","Africa/Nairobi|Africa/Asmera","Africa/Nairobi|Africa/Dar_es_Salaam","Africa/Nairobi|Africa/Djibouti","Africa/Nairobi|Africa/Kampala","Africa/Nairobi|Africa/Mogadishu","Africa/Nairobi|Indian/Antananarivo","Africa/Nairobi|Indian/Comoro","Africa/Nairobi|Indian/Mayotte","Africa/Tripoli|Libya","America/Adak|America/Atka","America/Adak|US/Aleutian","America/Anchorage|US/Alaska","America/Argentina/Buenos_Aires|America/Buenos_Aires","America/Argentina/Catamarca|America/Argentina/ComodRivadavia","America/Argentina/Catamarca|America/Catamarca","America/Argentina/Cordoba|America/Cordoba","America/Argentina/Cordoba|America/Rosario","America/Argentina/Jujuy|America/Jujuy","America/Argentina/Mendoza|America/Mendoza","America/Atikokan|America/Coral_Harbour","America/Chicago|US/Central","America/Curacao|America/Aruba","America/Curacao|America/Kralendijk","America/Curacao|America/Lower_Princes","America/Denver|America/Shiprock","America/Denver|Navajo","America/Denver|US/Mountain","America/Detroit|US/Michigan","America/Edmonton|Canada/Mountain","America/Fort_Wayne|America/Indiana/Indianapolis","America/Fort_Wayne|America/Indianapolis","America/Fort_Wayne|US/East-Indiana","America/Halifax|Canada/Atlantic","America/Havana|Cuba","America/Indiana/Knox|America/Knox_IN","America/Indiana/Knox|US/Indiana-Starke","America/Jamaica|Jamaica","America/Kentucky/Louisville|America/Louisville","America/Los_Angeles|US/Pacific","America/Los_Angeles|US/Pacific-New","America/Manaus|Brazil/West","America/Mazatlan|Mexico/BajaSur","America/Mexico_City|Mexico/General","America/New_York|US/Eastern","America/Noronha|Brazil/DeNoronha","America/Panama|America/Cayman","America/Phoenix|US/Arizona","America/Port_of_Spain|America/Anguilla","America/Port_of_Spain|America/Antigua","America/Port_of_Spain|America/Dominica","America/Port_of_Spain|America/Grenada","America/Port_of_Spain|America/Guadeloupe","America/Port_of_Spain|America/Marigot","America/Port_of_Spain|America/Montserrat","America/Port_of_Spain|America/St_Barthelemy","America/Port_of_Spain|America/St_Kitts","America/Port_of_Spain|America/St_Lucia","America/Port_of_Spain|America/St_Thomas","America/Port_of_Spain|America/St_Vincent","America/Port_of_Spain|America/Tortola","America/Port_of_Spain|America/Virgin","America/Regina|Canada/East-Saskatchewan","America/Regina|Canada/Saskatchewan","America/Rio_Branco|America/Porto_Acre","America/Rio_Branco|Brazil/Acre","America/Santiago|Chile/Continental","America/Sao_Paulo|Brazil/East","America/St_Johns|Canada/Newfoundland","America/Tijuana|America/Ensenada","America/Tijuana|America/Santa_Isabel","America/Tijuana|Mexico/BajaNorte","America/Toronto|America/Montreal","America/Toronto|Canada/Eastern","America/Vancouver|Canada/Pacific","America/Whitehorse|Canada/Yukon","America/Winnipeg|Canada/Central","Asia/Ashgabat|Asia/Ashkhabad","Asia/Bangkok|Asia/Phnom_Penh","Asia/Bangkok|Asia/Vientiane","Asia/Dhaka|Asia/Dacca","Asia/Dubai|Asia/Muscat","Asia/Ho_Chi_Minh|Asia/Saigon","Asia/Hong_Kong|Hongkong","Asia/Jerusalem|Asia/Tel_Aviv","Asia/Jerusalem|Israel","Asia/Kathmandu|Asia/Katmandu","Asia/Kolkata|Asia/Calcutta","Asia/Macau|Asia/Macao","Asia/Makassar|Asia/Ujung_Pandang","Asia/Nicosia|Europe/Nicosia","Asia/Qatar|Asia/Bahrain","Asia/Riyadh|Asia/Aden","Asia/Riyadh|Asia/Kuwait","Asia/Seoul|ROK","Asia/Shanghai|Asia/Chongqing","Asia/Shanghai|Asia/Chungking","Asia/Shanghai|Asia/Harbin","Asia/Shanghai|PRC","Asia/Singapore|Singapore","Asia/Taipei|ROC","Asia/Tehran|Iran","Asia/Thimphu|Asia/Thimbu","Asia/Tokyo|Japan","Asia/Ulaanbaatar|Asia/Ulan_Bator","Asia/Urumqi|Asia/Kashgar","Atlantic/Faroe|Atlantic/Faeroe","Atlantic/Reykjavik|Iceland","Australia/Adelaide|Australia/South","Australia/Brisbane|Australia/Queensland","Australia/Broken_Hill|Australia/Yancowinna","Australia/Darwin|Australia/North","Australia/Hobart|Australia/Tasmania","Australia/Lord_Howe|Australia/LHI","Australia/Melbourne|Australia/Victoria","Australia/Perth|Australia/West","Australia/Sydney|Australia/ACT","Australia/Sydney|Australia/Canberra","Australia/Sydney|Australia/NSW","Etc/GMT+0|Etc/GMT","Etc/GMT+0|Etc/GMT-0","Etc/GMT+0|Etc/GMT0","Etc/GMT+0|Etc/Greenwich","Etc/GMT+0|GMT","Etc/GMT+0|GMT+0","Etc/GMT+0|GMT-0","Etc/GMT+0|GMT0","Etc/GMT+0|Greenwich","Etc/UCT|UCT","Etc/UTC|Etc/Universal","Etc/UTC|Etc/Zulu","Etc/UTC|UTC","Etc/UTC|Universal","Etc/UTC|Zulu","Europe/Belgrade|Europe/Ljubljana","Europe/Belgrade|Europe/Podgorica","Europe/Belgrade|Europe/Sarajevo","Europe/Belgrade|Europe/Skopje","Europe/Belgrade|Europe/Zagreb","Europe/Chisinau|Europe/Tiraspol","Europe/Dublin|Eire","Europe/Helsinki|Europe/Mariehamn","Europe/Istanbul|Asia/Istanbul","Europe/Istanbul|Turkey","Europe/Lisbon|Portugal","Europe/London|Europe/Belfast","Europe/London|Europe/Guernsey","Europe/London|Europe/Isle_of_Man","Europe/London|Europe/Jersey","Europe/London|GB","Europe/London|GB-Eire","Europe/Moscow|W-SU","Europe/Oslo|Arctic/Longyearbyen","Europe/Oslo|Atlantic/Jan_Mayen","Europe/Prague|Europe/Bratislava","Europe/Rome|Europe/San_Marino","Europe/Rome|Europe/Vatican","Europe/Warsaw|Poland","Europe/Zurich|Europe/Busingen","Europe/Zurich|Europe/Vaduz","Pacific/Auckland|Antarctica/McMurdo","Pacific/Auckland|Antarctica/South_Pole","Pacific/Auckland|NZ","Pacific/Chatham|NZ-CHAT","Pacific/Chuuk|Pacific/Truk","Pacific/Chuuk|Pacific/Yap","Pacific/Easter|Chile/EasterIsland","Pacific/Guam|Pacific/Saipan","Pacific/Honolulu|Pacific/Johnston","Pacific/Honolulu|US/Hawaii","Pacific/Kwajalein|Kwajalein","Pacific/Pago_Pago|Pacific/Midway","Pacific/Pago_Pago|Pacific/Samoa","Pacific/Pago_Pago|US/Samoa","Pacific/Pohnpei|Pacific/Ponape"]}),a}); diff --git a/setup/performance-toolkit/profiles/ce/attributeSets.xml b/setup/performance-toolkit/profiles/ce/attributeSets.xml new file mode 100644 index 0000000000000000000000000000000000000000..0db403e9969c079da00562f119233d24ea208a6b --- /dev/null +++ b/setup/performance-toolkit/profiles/ce/attributeSets.xml @@ -0,0 +1,132 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<attribute_sets> <!-- Content of Attribute Sets --> + <attribute_set> + <name>Attribute Set 1</name> + <attributes> + <attribute> + <is_required>1</is_required> + <is_visible_on_front>1</is_visible_on_front> + <is_visible_in_advanced_search>1</is_visible_in_advanced_search> + <attribute_code>mycolor</attribute_code> + <backend_type></backend_type> + <is_searchable>1</is_searchable> + <is_filterable>1</is_filterable> + <is_filterable_in_search>1</is_filterable_in_search> + <frontend_label>my color</frontend_label> + <frontend_input>select</frontend_input> + <default_value>my yellow</default_value> + <options> + <option> + <label>my green</label> + <value>my green</value> + </option> + <option> + <label>my red</label> + <value>my red</value> + </option> + <option> + <label>my yellow</label> + <value>my yellow</value> + </option> + </options> + </attribute> + <attribute> + <is_required>1</is_required> + <is_visible_on_front>1</is_visible_on_front> + <is_visible_in_advanced_search>1</is_visible_in_advanced_search> + <attribute_code>mysize</attribute_code> + <backend_type></backend_type> + <is_searchable>1</is_searchable> + <is_filterable>1</is_filterable> + <is_filterable_in_search>1</is_filterable_in_search> + <frontend_label>my size</frontend_label> + <frontend_input>select</frontend_input> + <default_value>my large</default_value> + <options> + <option> + <label>my small</label> + <value>my small</value> + </option> + <option> + <label>my medium</label> + <value>my medium</value> + </option> + <option> + <label>my large</label> + <value>my large</value> + </option> + </options> + </attribute> + </attributes> + </attribute_set> + <attribute_set> + <name>Attribute Set 2</name> + <attributes> + <attribute> + <is_required>1</is_required> + <is_visible_on_front>1</is_visible_on_front> + <is_visible_in_advanced_search>1</is_visible_in_advanced_search> + <attribute_code>attributeset2attribute1</attribute_code> + <backend_type></backend_type> + <is_searchable>1</is_searchable> + <is_filterable>1</is_filterable> + <is_filterable_in_search>1</is_filterable_in_search> + <frontend_label>Attribute Set 2 - Attribute 1</frontend_label> + <frontend_input>select</frontend_input> + <default_value>attributeset2attribute1option1</default_value> + <options> + <option> + <label>Attribute Set 2 - Attribute 1 - Option a</label> + <value>attributeset2attribute1option1</value> + </option> + <option> + <label>Attribute Set 2 - Attribute 1 - Option b</label> + <value>attributeset2attribute1option2</value> + </option> + <option> + <label>Attribute Set 2 - Attribute 1 - Option c</label> + <value>attributeset2attribute1option3</value> + </option> + </options> + </attribute> + </attributes> + </attribute_set> + <attribute_set> + <name>Attribute Set 3</name> + <attributes> + <attribute> + <is_required>1</is_required> + <is_visible_on_front>1</is_visible_on_front> + <is_visible_in_advanced_search>1</is_visible_in_advanced_search> + <attribute_code>attributeset3attribute1</attribute_code> + <backend_type></backend_type> + <is_searchable>1</is_searchable> + <is_filterable>1</is_filterable> + <is_filterable_in_search>1</is_filterable_in_search> + <frontend_label>Attribute Set 3 - Attribute 1</frontend_label> + <frontend_input>select</frontend_input> + <default_value>attributeset3attribute1option1</default_value> + <options> + <option> + <label>Attribute Set 3 - Attribute 1 - Option a</label> + <value>attributeset3attribute1option1</value> + </option> + <option> + <label>Attribute Set 3 - Attribute 1 - Option b</label> + <value>attributeset3attribute1option2</value> + </option> + <option> + <label>Attribute Set 3 - Attribute 1 - Option c</label> + <value>attributeset3attribute1option3</value> + </option> + </options> + </attribute> + </attributes> + </attribute_set> +</attribute_sets> \ No newline at end of file diff --git a/setup/performance-toolkit/profiles/ce/extra_large.xml b/setup/performance-toolkit/profiles/ce/extra_large.xml index 41561e54f787b417ec5be855e607a2436b1d0abe..c9bf96ad4f4cab4cb8a6c1aacbe6e89a673e842c 100644 --- a/setup/performance-toolkit/profiles/ce/extra_large.xml +++ b/setup/performance-toolkit/profiles/ce/extra_large.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<config> +<config xmlns:xi="http://www.w3.org/2001/XInclude"> <profile> <websites>5</websites> <!-- Number of websites to generate --> <store_groups>5</store_groups> <!--Number of stores--> @@ -68,5 +68,8 @@ <set_scheduled>true</set_scheduled> </indexer> </indexers> + <xi:include href="searchTerms.xml" /> + <xi:include href="searchConfig.xml" /> + <xi:include href="attributeSets.xml" /> </profile> </config> diff --git a/setup/performance-toolkit/profiles/ce/large.xml b/setup/performance-toolkit/profiles/ce/large.xml index ec521527a438406cf08b0211944347612bca5743..07e7ac33023b17d5d626986b76d84e88b3f7314b 100644 --- a/setup/performance-toolkit/profiles/ce/large.xml +++ b/setup/performance-toolkit/profiles/ce/large.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<config> +<config xmlns:xi="http://www.w3.org/2001/XInclude"> <profile> <websites>3</websites> <!-- Number of websites to generate --> <store_groups>3</store_groups> <!--Number of stores--> @@ -68,5 +68,8 @@ <set_scheduled>true</set_scheduled> </indexer> </indexers> + <xi:include href="searchTerms.xml" /> + <xi:include href="searchConfig.xml" /> + <xi:include href="attributeSets.xml" /> </profile> </config> diff --git a/setup/performance-toolkit/profiles/ce/medium.xml b/setup/performance-toolkit/profiles/ce/medium.xml index d2125b0586b1244553277c5cafdbe30e7bb5020a..6b1b289e823bab41a6d637350037b64c39e96935 100644 --- a/setup/performance-toolkit/profiles/ce/medium.xml +++ b/setup/performance-toolkit/profiles/ce/medium.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<config> +<config xmlns:xi="http://www.w3.org/2001/XInclude"> <profile> <websites>1</websites> <!-- Number of websites to generate --> <store_groups>2</store_groups> <!--Number of stores--> @@ -68,5 +68,8 @@ <set_scheduled>false</set_scheduled> </indexer> </indexers> + <xi:include href="searchTerms.xml" /> + <xi:include href="searchConfig.xml" /> + <xi:include href="attributeSets.xml" /> </profile> </config> diff --git a/setup/performance-toolkit/profiles/ce/searchConfig.xml b/setup/performance-toolkit/profiles/ce/searchConfig.xml new file mode 100644 index 0000000000000000000000000000000000000000..55c2caea2482c271af5cc2aa97e243fea9c6f6e3 --- /dev/null +++ b/setup/performance-toolkit/profiles/ce/searchConfig.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<search_config> <!-- Search configuration for Simple/Configurable products --> + <max_amount_of_words_description>200</max_amount_of_words_description> + <max_amount_of_words_short_description>20</max_amount_of_words_short_description> + <min_amount_of_words_description>20</min_amount_of_words_description> + <min_amount_of_words_short_description>5</min_amount_of_words_short_description> +</search_config> diff --git a/setup/performance-toolkit/profiles/ce/searchTerms.xml b/setup/performance-toolkit/profiles/ce/searchTerms.xml new file mode 100644 index 0000000000000000000000000000000000000000..b5713ac4644f661a586564827283c6b7c973b51e --- /dev/null +++ b/setup/performance-toolkit/profiles/ce/searchTerms.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<search_terms> <!-- Content of Search Terms --> + <search_term> + <term>iphone 6</term> + <count>50</count> + </search_term> + <search_term> + <term>galaxy s3</term> + <count>100</count> + </search_term> +</search_terms> \ No newline at end of file diff --git a/setup/performance-toolkit/profiles/ce/small.xml b/setup/performance-toolkit/profiles/ce/small.xml index d935a90299b6063d1b06d995647044c9d0a5b23d..4f9b436666f8c743c0fd0058c6fc747944506c9d 100644 --- a/setup/performance-toolkit/profiles/ce/small.xml +++ b/setup/performance-toolkit/profiles/ce/small.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<config> +<config xmlns:xi="http://www.w3.org/2001/XInclude"> <profile> <websites>1</websites> <!-- Number of websites to generate --> <store_groups>1</store_groups> <!--Number of stores--> @@ -68,5 +68,8 @@ <set_scheduled>false</set_scheduled> </indexer> </indexers> + <xi:include href="searchTerms.xml" /> + <xi:include href="searchConfig.xml" /> + <xi:include href="attributeSets.xml" /> </profile> </config> diff --git a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php index d91a1633ef03848eb1a73a123b9e5f87d6f253d4..f70c36cec65dcd5fb868f12b7144fb46e58c3a7c 100644 --- a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php +++ b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php @@ -78,7 +78,12 @@ class GenerateFixturesCommand extends Command $output->writeln('<info>Generating profile with following params:</info>'); foreach ($fixtureModel->getParamLabels() as $configKey => $label) { - $output->writeln('<info> |- ' . $label . ': ' . $fixtureModel->getValue($configKey) . '</info>'); + $output->writeln( + '<info> |- ' . $label . ': ' . (is_array($fixtureModel->getValue($configKey)) === true + ? sizeof( + $fixtureModel->getValue($configKey)[array_keys($fixtureModel->getValue($configKey))[0]] + ) : $fixtureModel->getValue($configKey)) . '</info>' + ); } /** @var $config \Magento\Indexer\Model\Config */ diff --git a/setup/src/Magento/Setup/Fixtures/AttributeSetsFixture.php b/setup/src/Magento/Setup/Fixtures/AttributeSetsFixture.php new file mode 100644 index 0000000000000000000000000000000000000000..5617eca9a0e7e24694c151d508ed09a4cd547999 --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/AttributeSetsFixture.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Fixtures; + +/** + * Class AttributeSetFixture + */ +class AttributeSetsFixture extends Fixture +{ + /** + * @var int + */ + protected $priority = 25; + + /** + * {@inheritdoc} + */ + public function execute() + { + $attributeSets = $this->fixtureModel->getValue('attribute_sets', null); + if ($attributeSets === null) { + return; + } + $this->fixtureModel->resetObjectManager(); + /** @var \Magento\Catalog\Api\AttributeSetManagementInterface $attributeSetManagement */ + $attributeSetManagement = $this->fixtureModel->getObjectManager()->create( + \Magento\Catalog\Api\AttributeSetManagementInterface::class + ); + /** @var \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface $attributeGroupRepository */ + $attributeGroupRepository = $this->fixtureModel->getObjectManager()->create( + \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface::class + ); + + foreach ($attributeSets['attribute_set'] as $attributeSetData) { + //Create Attribute Set + /** @var \Magento\Eav\Api\Data\AttributeSetInterfaceFactory $attributeSetFactory */ + $attributeSetFactory = $this->fixtureModel->getObjectManager()->create( + \Magento\Eav\Api\Data\AttributeSetInterfaceFactory::class + ); + $attributeSet = $attributeSetFactory->create(); + $attributeSet->setAttributeSetName($attributeSetData['name']); + $attributeSet->setEntityTypeId(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE); + + $result = $attributeSetManagement->create($attributeSet, 4); + $attributeSetId = $result->getAttributeSetId(); + + //Create Attribute Group + /** @var \Magento\Eav\Api\Data\AttributeGroupInterfaceFactory $attributeGroupFactory */ + $attributeGroupFactory = $this->fixtureModel->getObjectManager()->create( + \Magento\Eav\Api\Data\AttributeGroupInterfaceFactory::class + ); + $attributeGroup = $attributeGroupFactory->create(); + $attributeGroup->setAttributeGroupName($result->getAttributeSetName() . ' - Group'); + $attributeGroup->setAttributeSetId($attributeSetId); + $attributeGroupRepository->save($attributeGroup); + $attributeGroupId = $attributeGroup->getAttributeGroupId(); + + /** @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository */ + $attributeRepository = $this->fixtureModel->getObjectManager()->create( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface::class + ); + /** @var \Magento\Catalog\Api\ProductAttributeManagementInterface $attributeManagementManagement */ + $attributeManagement = $this->fixtureModel->getObjectManager()->create( + \Magento\Catalog\Api\ProductAttributeManagementInterface::class + ); + + $attributesData = array_key_exists(0, $attributeSetData['attributes']['attribute']) + ? $attributeSetData['attributes']['attribute'] : [$attributeSetData['attributes']['attribute']]; + foreach ($attributesData as $attributeData) { + //Create Attribute + /** @var \Magento\Catalog\Api\Data\ProductAttributeInterfaceFactory $attributeFactory */ + $attributeFactory = $this->fixtureModel->getObjectManager()->create( + \Magento\Catalog\Api\Data\ProductAttributeInterfaceFactory::class + ); + + $optionsData = array_key_exists(0, $attributeData['options']['option']) + ? $attributeData['options']['option'] : [$attributeData['options']['option']]; + $options = []; + foreach ($optionsData as $optionData) { + /** @var \Magento\Eav\Api\Data\AttributeOptionInterfaceFactory $optionFactory */ + $optionFactory = $this->fixtureModel->getObjectManager()->create( + \Magento\Eav\Api\Data\AttributeOptionInterfaceFactory::class + ); + $option = $optionFactory->create(['data' => $optionData]); + $options[] = $option; + } + + $attribute = $attributeFactory->create(['data' => $attributeData]); + $attribute->setOptions($options); + + $result = $attributeRepository->save($attribute); + $attributeId = $result->getAttributeId(); + + //Associate Attribute to Attribute Set + $sortOrder = 3; + $attributeManagement->assign($attributeSetId, $attributeGroupId, $attributeId, $sortOrder); + } + } + } + + /** + * {@inheritdoc} + */ + public function getActionTitle() + { + return 'Generating attribute sets'; + } + + /** + * {@inheritdoc} + */ + public function introduceParamLabels() + { + return [ + 'attribute_sets' => 'Attribute Sets' + ]; + } +} diff --git a/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php b/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php index 5142efc2da6614cfd6253b02cf7a30b69fc07663..2340de71cf78363ed00fd9248577e7479b6dc012 100644 --- a/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php @@ -6,19 +6,25 @@ namespace Magento\Setup\Fixtures; -use Magento\Setup\Model\Complex\Generator; +use Magento\Setup\Model\DataGenerator; use Magento\Setup\Model\Complex\Pattern; +use Magento\Setup\Model\Complex\Generator; /** * Class ConfigurableProductsFixture */ -class ConfigurableProductsFixture extends Fixture +class ConfigurableProductsFixture extends SimpleProductsFixture { /** * @var int */ protected $priority = 50; + /** + * @var array + */ + protected $searchConfig; + //@codingStandardsIgnoreStart /** * Get CSV template headers @@ -31,6 +37,7 @@ class ConfigurableProductsFixture extends Fixture 'sku', 'store_view_code', 'attribute_set_code', + 'additional_attributes', 'product_type', 'categories', 'product_websites', @@ -137,22 +144,38 @@ class ConfigurableProductsFixture extends Fixture } /** - * @param callable $productCategory - * @param callable $productWebsite - * @param string $variation + * @param Closure|mixed $productCategoryClosure + * @param Closure|mixed $productWebsiteClosure + * @param Closure|mixed $shortDescriptionClosure + * @param Closure|mixed $descriptionClosure + * @param Closure|mixed $priceClosure + * @param Closure|mixed $attributeSetClosure + * @param Closure|mixed $additionalAttributesClosure + * @param string $variationClosure * @param string $suffix * @return array * @SuppressWarnings(PHPMD) */ - private function generateConfigurableProduct($productCategory, $productWebsite, $variation, $suffix) + private function generateConfigurableProduct( + $productCategoryClosure, + $productWebsiteClosure, + $shortDescriptionClosure, + $descriptionClosure, + $priceClosure, + $attributeSetClosure, + $additionalAttributesClosure, + $variationClosure, + $suffix + ) { return [ 'sku' => 'Configurable Product %s' . $suffix, 'store_view_code' => '', - 'attribute_set_code' => 'Default', + 'attribute_set_code' => $attributeSetClosure, + 'additional_attributes' => $additionalAttributesClosure, 'product_type' => 'configurable', - 'categories' => $productCategory, - 'product_websites' => $productWebsite, + 'categories' => $productCategoryClosure, + 'product_websites' => $productWebsiteClosure, 'color' => '', 'configurable_variation' => '', 'cost' => '', @@ -162,7 +185,7 @@ class ConfigurableProductsFixture extends Fixture 'custom_design_from' => '', 'custom_design_to' => '', 'custom_layout_update' => '', - 'description' => '<p>Configurable product description %s</p>', + 'description' => $descriptionClosure, 'enable_googlecheckout' => '1', 'gallery' => '', 'gift_message_available' => '', @@ -184,12 +207,12 @@ class ConfigurableProductsFixture extends Fixture 'news_to_date' => '', 'options_container' => 'Block after Info Column', 'page_layout' => '', - 'price' => '10', + 'price' => $priceClosure, 'quantity_and_stock_status' => 'In Stock', 'related_tgtr_position_behavior' => '', 'related_tgtr_position_limit' => '', 'required_options' => '1', - 'short_description' => '', + 'short_description' => $shortDescriptionClosure, 'small_image' => '', 'small_image_label' => '', 'special_from_date' => '', @@ -244,32 +267,51 @@ class ConfigurableProductsFixture extends Fixture '_media_label' => '', '_media_position' => '', '_media_is_disabled' => '', - 'configurable_variations' => $variation, + 'configurable_variations' => $variationClosure, ]; } /** * Get CSV template rows * - * @param Closure|mixed $productCategory - * @param Closure|mixed $productWebsite + * @param Closure|mixed $productCategoryClosure + * @param Closure|mixed $productWebsiteClosure + * @param Closure|mixed $shortDescriptionClosure + * @param Closure|mixed $descriptionClosure + * @param Closure|mixed $priceClosure + * @param Closure|mixed $attributeSetClosure + * @param Closure|mixed $additionalAttributesClosure + * @param Closure|mixed $variationClosure + * @param int $optionsNumber + * @param string $suffix * * @SuppressWarnings(PHPMD) * * @return array */ - protected function getRows($productCategory, $productWebsite, $optionsNumber, $suffix = '') + protected function getRows( + $productCategoryClosure, + $productWebsiteClosure, + $shortDescriptionClosure, + $descriptionClosure, + $priceClosure, + $attributeSetClosure, + $additionalAttributesClosure, + $variationClosure, + $optionsNumber, + $suffix = '' + ) { $data = []; - $variation = []; for ($i = 1; $i <= $optionsNumber; $i++) { $productData = [ 'sku' => "Configurable Product %s-option {$i}{$suffix}", 'store_view_code' => '', - 'attribute_set_code' => 'Default', + 'attribute_set_code' => $attributeSetClosure, + 'additional_attributes' => $additionalAttributesClosure, 'product_type' => 'simple', - 'categories' => $productCategory, - 'product_websites' => $productWebsite, + 'categories' => $productCategoryClosure, + 'product_websites' => $productWebsiteClosure, 'color' => '', 'configurable_variation' => "option {$i}", 'cost' => '', @@ -279,7 +321,7 @@ class ConfigurableProductsFixture extends Fixture 'custom_design_from' => '', 'custom_design_to' => '', 'custom_layout_update' => '', - 'description' => '<p>Configurable product description %s</p>', + 'description' => $descriptionClosure, 'enable_googlecheckout' => '1', 'gallery' => '', 'gift_message_available' => '', @@ -301,12 +343,12 @@ class ConfigurableProductsFixture extends Fixture 'news_to_date' => '', 'options_container' => 'Block after Info Column', 'page_layout' => '', - 'price' => function () { return mt_rand(1, 1000) / 10; }, + 'price' => $priceClosure, 'quantity_and_stock_status' => 'In Stock', 'related_tgtr_position_behavior' => '', 'related_tgtr_position_limit' => '', 'required_options' => '0', - 'short_description' => '', + 'short_description' => $shortDescriptionClosure, 'small_image' => '', 'small_image_label' => '', 'special_from_date' => '', @@ -366,21 +408,18 @@ class ConfigurableProductsFixture extends Fixture '_media_position' => '', '_media_is_disabled' => '', ]; - - $variation[] = implode( - ',', - [ - 'sku=' . $productData['sku'], - 'configurable_variation=' . $productData['configurable_variation'], - ] - ); $data[] = $productData; } $data[] = $this->generateConfigurableProduct( - $productCategory, - $productWebsite, - implode('|', $variation), + $productCategoryClosure, + $productWebsiteClosure, + $shortDescriptionClosure, + $descriptionClosure, + $priceClosure, + $attributeSetClosure, + $additionalAttributesClosure, + $variationClosure, $suffix ); return $data; @@ -388,26 +427,141 @@ class ConfigurableProductsFixture extends Fixture /** * {@inheritdoc} + * @SuppressWarnings(PHPMD) */ public function execute() { - $configurableCount = $this->fixtureModel->getValue('configurable_products', 0); - if (!$configurableCount) { + $configurableProductsCount = $this->fixtureModel->getValue('configurable_products', 0); + if (!$configurableProductsCount) { return; } - $this->fixtureModel->resetObjectManager(); + $simpleProductsCount = $this->fixtureModel->getValue('simple_products', 0); + $maxAmountOfWordsDescription = $this->getSearchConfigValue('max_amount_of_words_description'); + $maxAmountOfWordsShortDescription = $this->getSearchConfigValue('max_amount_of_words_short_description'); + $minAmountOfWordsDescription = $this->getSearchConfigValue('min_amount_of_words_description'); + $minAmountOfWordsShortDescription = $this->getSearchConfigValue('min_amount_of_words_short_description'); + $attributes = $this->getAttributes(); + $searchTerms = $this->getSearchTerms(); + $this->fixtureModel->resetObjectManager(); $result = $this->getCategoriesAndWebsites(); - + $variationCount = $this->fixtureModel->getValue('configurable_products_variation', 3); $result = array_values($result); + $dataGenerator = new DataGenerator(realpath(__DIR__ . '/' . 'dictionary.csv')); - $productWebsite = function ($index) use ($result) { + $productWebsiteClosure = function ($index) use ($result) { return $result[$index % count($result)][0]; }; - $productCategory = function ($index) use ($result) { + $productCategoryClosure = function ($index) use ($result) { return $result[$index % count($result)][2] . '/' . $result[$index % count($result)][1]; }; - + $shortDescriptionClosure = function ($index) + use ( + $searchTerms, + $simpleProductsCount, + $configurableProductsCount, + $dataGenerator, + $maxAmountOfWordsShortDescription, + $minAmountOfWordsShortDescription + ) + { + $count = $searchTerms === null + ? 0 + : round( + $searchTerms[$index % count($searchTerms)]['count'] * ( + $configurableProductsCount / ($simpleProductsCount + $configurableProductsCount) + ) + ); + mt_srand($index); + return $dataGenerator->generate( + $minAmountOfWordsShortDescription, + $maxAmountOfWordsShortDescription, + 'shortDescription-' . $index + ) . ($index <= ($count * count($searchTerms)) ? ' ' + . $searchTerms[$index % count($searchTerms)]['term'] : ''); + }; + $descriptionClosure = function ($index) + use ( + $searchTerms, + $simpleProductsCount, + $configurableProductsCount, + $dataGenerator, + $maxAmountOfWordsDescription, + $minAmountOfWordsDescription + ) + { + $count = $searchTerms === null + ? 0 + : round( + $searchTerms[$index % count($searchTerms)]['count'] * ( + $configurableProductsCount / ($simpleProductsCount + $configurableProductsCount) + ) + ); + mt_srand($index); + return $dataGenerator->generate( + $minAmountOfWordsDescription, + $maxAmountOfWordsDescription, + 'description-' . $index + ) . ($index <= ($count * count($searchTerms)) + ? ' ' . $searchTerms[$index % count($searchTerms)]['term'] : ''); + }; + $priceClosure = function($index) { + mt_srand($index); + switch (mt_rand(0,3)) { + case 0: return 9.99; + case 1: return 5; + case 2: return 1; + case 3: return mt_rand(1,10000)/10; + } + }; + $attributeSetClosure = function($index) use ($attributes, $result) { + mt_srand($index); + $attributeSet = (count(array_keys($attributes)) > (($index - 1) % count($result)) + ? array_keys($attributes)[mt_rand(0, count(array_keys($attributes)) - 1)] : 'Default'); + return $attributeSet; + }; + $variationClosure = function($index, $variationIndex) use ($attributes, $result, $variationCount) { + mt_srand($index); + $attributeSetCode = (count(array_keys($attributes)) > (($index - 1) % count($result)) + ? array_keys($attributes)[mt_rand(0, count(array_keys($attributes)) - 1)] : 'Default'); + $skus = []; + for ($i=1; $i <= $variationCount; $i++) { + $skus[] = 'sku=Configurable Product ' . $index . '-option ' . $i; + } + $values = []; + if ($attributeSetCode == 'Default') { + for ($i=1; $i <= $variationCount; $i++) { + $values[] = 'configurable_variation=option ' . $i; + } + } else { + for ($i=$variationCount; $i > 0; $i--) { + $attributeValues = ''; + foreach ($attributes[$attributeSetCode] as $attribute) { + $attributeValues = $attributeValues . $attribute['name'] . "=" . + $attribute['values'][($variationIndex - $i) % count($attribute['values'])] . ","; + } + $values [] = $attributeValues; + } + } + $variations = []; + for ($i=0; $i < $variationCount; $i++) { + $variations[] = trim(implode(",",[$skus[$i],$values[$i]]), ","); + } + return implode("|",$variations); + }; + $additionalAttributesClosure = function($index, $variationIndex) use ($attributes, $result) { + $attributeValues = ''; + mt_srand($index); + $attributeSetCode = (count(array_keys($attributes)) > (($index - 1) % count($result)) + ? array_keys($attributes)[mt_rand(0, count(array_keys($attributes)) - 1)] : 'Default'); + if ($attributeSetCode !== 'Default' ) { + foreach ($attributes[$attributeSetCode] as $attribute) { + $attributeValues = $attributeValues . $attribute['name'] . "=" . + $attribute['values'][$variationIndex % count($attribute['values'])] . ","; + } + } + return trim($attributeValues, ","); + }; /** * Create configurable products */ @@ -415,9 +569,15 @@ class ConfigurableProductsFixture extends Fixture $pattern->setHeaders($this->getHeaders()); $pattern->setRowsSet( $this->getRows( - $productCategory, - $productWebsite, - $this->fixtureModel->getValue('configurable_products_variation', 3) + $productCategoryClosure, + $productWebsiteClosure, + $shortDescriptionClosure, + $descriptionClosure, + $priceClosure, + $attributeSetClosure, + $additionalAttributesClosure, + $variationClosure, + $variationCount ) ); @@ -435,7 +595,7 @@ class ConfigurableProductsFixture extends Fixture $source = $this->fixtureModel->getObjectManager()->create( Generator::class, - ['rowPattern' => $pattern, 'count' => $configurableCount] + ['rowPattern' => $pattern, 'count' => $configurableProductsCount] ); // it is not obvious, but the validateSource() will actually save import queue data to DB if (!$import->validateSource($source)) { @@ -467,9 +627,10 @@ class ConfigurableProductsFixture extends Fixture } /** + * @override * @return array */ - private function getCategoriesAndWebsites() + protected function getCategoriesAndWebsites() { /** @var \Magento\Store\Model\StoreManager $storeManager */ $storeManager = $this->fixtureModel->getObjectManager()->get(\Magento\Store\Model\StoreManager::class); diff --git a/setup/src/Magento/Setup/Fixtures/FixtureModel.php b/setup/src/Magento/Setup/Fixtures/FixtureModel.php index 118b29cb4306bb6e05580eea3bc0da207a72835d..44173a32c3e771ec2c43480a73c380ad2a2691ab 100644 --- a/setup/src/Magento/Setup/Fixtures/FixtureModel.php +++ b/setup/src/Magento/Setup/Fixtures/FixtureModel.php @@ -206,7 +206,9 @@ class FixtureModel if (!is_readable($filename)) { throw new \Exception("Profile configuration file `{$filename}` is not readable or does not exists."); } - $this->config = $this->fileParser->load($filename)->xmlToArray(); + $this->fileParser->getDom()->load($filename); + $this->fileParser->getDom()->xinclude(); + $this->config = $this->fileParser->xmlToArray(); } /** diff --git a/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php b/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php index 6de4eaf91529fb3bc12cffe4d78aba238bc2fc05..fbb28d35269b177762410e8da7560612dd64db9c 100644 --- a/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/SimpleProductsFixture.php @@ -6,6 +6,7 @@ namespace Magento\Setup\Fixtures; +use Magento\Setup\Model\DataGenerator; use Magento\Setup\Model\Generator; /** @@ -18,8 +19,14 @@ class SimpleProductsFixture extends Fixture */ protected $priority = 30; + /** + * @var array + */ + protected $searchConfig; + /** * {@inheritdoc} + * @SuppressWarnings(PHPMD) */ public function execute() { @@ -27,48 +34,16 @@ class SimpleProductsFixture extends Fixture if (!$simpleProductsCount) { return; } + $configurableProductsCount = $this->fixtureModel->getValue('configurable_products', 0); + $maxAmountOfWordsDescription = $this->getSearchConfigValue('max_amount_of_words_description'); + $maxAmountOfWordsShortDescription = $this->getSearchConfigValue('max_amount_of_words_short_description'); + $minAmountOfWordsDescription = $this->getSearchConfigValue('min_amount_of_words_description'); + $minAmountOfWordsShortDescription = $this->getSearchConfigValue('min_amount_of_words_short_description'); + $searchTerms = $this->getSearchTerms(); + $attributes = $this->getAttributes(); $this->fixtureModel->resetObjectManager(); - - /** @var \Magento\Store\Model\StoreManager $storeManager */ - $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); - /** @var $category \Magento\Catalog\Model\Category */ - $category = $this->fixtureModel->getObjectManager()->get(\Magento\Catalog\Model\Category::class); - - $result = []; - //Get all websites - $websites = $storeManager->getWebsites(); - foreach ($websites as $website) { - $websiteCode = $website->getCode(); - //Get all groups - $websiteGroups = $website->getGroups(); - foreach ($websiteGroups as $websiteGroup) { - $websiteGroupRootCategory = $websiteGroup->getRootCategoryId(); - $category->load($websiteGroupRootCategory); - $categoryResource = $category->getResource(); - //Get all categories - $resultsCategories = $categoryResource->getAllChildren($category); - foreach ($resultsCategories as $resultsCategory) { - $category->load($resultsCategory); - $structure = explode('/', $category->getPath()); - $pathSize = count($structure); - if ($pathSize > 1) { - $path = []; - for ($i = 0; $i < $pathSize; $i++) { - $path[] = $category->load($structure[$i])->getName(); - } - array_shift($path); - $resultsCategoryName = implode('/', $path); - } else { - $resultsCategoryName = $category->getName(); - } - //Deleted root categories - if (trim($resultsCategoryName) != '') { - $result[$resultsCategory] = [$websiteCode, $resultsCategoryName]; - } - } - } - } - $result = array_values($result); + $result = $this->getCategoriesAndWebsites(); + $dataGenerator = new DataGenerator(realpath(__DIR__ . '/' . 'dictionary.csv')); $productWebsite = function ($index) use ($result) { return $result[$index % count($result)][0]; @@ -76,10 +51,92 @@ class SimpleProductsFixture extends Fixture $productCategory = function ($index) use ($result) { return $result[$index % count($result)][1]; }; - - $generator = new Generator( - $this->getPattern($productWebsite, $productCategory), - $simpleProductsCount + $shortDescription = function ($index) use ( + $searchTerms, + $simpleProductsCount, + $configurableProductsCount, + $dataGenerator, + $maxAmountOfWordsShortDescription, + $minAmountOfWordsShortDescription + ) { + $count = $searchTerms === null + ? 0 + : round( + $searchTerms[$index % count($searchTerms)]['count'] * ( + $simpleProductsCount / ($simpleProductsCount + $configurableProductsCount) + ) + ); + return $dataGenerator->generate( + $minAmountOfWordsShortDescription, + $maxAmountOfWordsShortDescription + ) . ($index <= ($count * count($searchTerms)) ? ' ' + . $searchTerms[$index % count($searchTerms)]['term'] : ''); + }; + $description = function ($index) use ( + $searchTerms, + $simpleProductsCount, + $configurableProductsCount, + $dataGenerator, + $maxAmountOfWordsDescription, + $minAmountOfWordsDescription + ) { + $count = $searchTerms === null + ? 0 + : round( + $searchTerms[$index % count($searchTerms)]['count'] * ( + $simpleProductsCount / ($simpleProductsCount + $configurableProductsCount) + ) + ); + return $dataGenerator->generate( + $minAmountOfWordsDescription, + $maxAmountOfWordsDescription + ) . ($index <= ($count * count($searchTerms)) ? ' ' + . $searchTerms[$index % count($searchTerms)]['term'] : ''); + }; + $price = function () { + switch (mt_rand(0, 3)) { + case 0: + return 9.99; + case 1: + return 5; + case 2: + return 1; + case 3: + return mt_rand(1, 10000)/10; + } + }; + $attributeSet = function ($index) use ($attributes, $result) { + mt_srand($index); + return (count(array_keys($attributes)) > (($index - 1) % count($result)) + ? array_keys($attributes)[mt_rand(0, count(array_keys($attributes)) - 1)] : 'Default'); + }; + $additionalAttributes = function ($index) use ($attributes, $result) { + $attributeValues = ''; + mt_srand($index); + $attributeSetCode = (count(array_keys($attributes)) > (($index - 1) % count($result)) + ? array_keys($attributes)[mt_rand(0, count(array_keys($attributes)) - 1)] : 'Default'); + if ($attributeSetCode !== 'Default') { + foreach ($attributes[$attributeSetCode] as $attribute) { + $attributeValues = $attributeValues . $attribute['name'] . "=" . + $attribute['values'][mt_rand(0, count($attribute['values']) - 1)] . ","; + } + } + return trim($attributeValues, ","); + }; + $generator = $this->fixtureModel->getObjectManager()->create( + Generator::class, + [ + 'rowPattern' => $this->getPattern( + $productWebsite, + $productCategory, + $shortDescription, + $description, + $price, + $attributeSet, + $additionalAttributes + ), + 'limit' => $simpleProductsCount + ] ); /** @var \Magento\ImportExport\Model\Import $import */ $import = $this->fixtureModel->getObjectManager()->create( @@ -93,9 +150,13 @@ class SimpleProductsFixture extends Fixture ] ); // it is not obvious, but the validateSource() will actually save import queue data to DB - $import->validateSource($generator); + if (!$import->validateSource($generator)) { + throw new \Exception($import->getFormatedLogTrace()); + } // this converts import queue into actual entities - $import->importSource(); + if (!$import->importSource()) { + throw new \Exception($import->getFormatedLogTrace()); + } } /** @@ -103,21 +164,34 @@ class SimpleProductsFixture extends Fixture * * @param Closure|int|string $productWebsiteClosure * @param Closure|int|string $productCategoryClosure + * @param Closure|int|string $shortDescriptionClosure + * @param Closure|int|string $descriptionClosure + * @param Closure|int|string $priceClosure + * @param Closure|int|string $attributeSetClosure + * @param Closure|int|string $additionalAttributesClosure * @return array */ - protected function getPattern($productWebsiteClosure, $productCategoryClosure) - { + protected function getPattern( + $productWebsiteClosure, + $productCategoryClosure, + $shortDescriptionClosure, + $descriptionClosure, + $priceClosure, + $attributeSetClosure, + $additionalAttributesClosure + ) { return [ - 'attribute_set_code' => 'Default', + 'attribute_set_code' => $attributeSetClosure, + 'additional_attributes' => $additionalAttributesClosure, 'product_type' => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, 'product_websites' => $productWebsiteClosure, 'categories' => $productCategoryClosure, 'name' => 'Simple Product %s', - 'short_description' => 'Short simple product description %s', + 'short_description' => $shortDescriptionClosure, 'weight' => 1, - 'description' => 'Full simple product Description %s', + 'description' => $descriptionClosure, 'sku' => 'product_dynamic_%s', - 'price' => 10, + 'price' => $priceClosure, 'visibility' => 'Catalog, Search', 'product_online' => \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED, 'tax_class_name' => 'Taxable Goods', @@ -155,4 +229,112 @@ class SimpleProductsFixture extends Fixture 'simple_products' => 'Simple products' ]; } + + /** + * @return array + */ + protected function getAttributes() + { + $attributeSets = $this->fixtureModel->getValue('attribute_sets', null); + $attributes = []; + + if ($attributeSets !== null && array_key_exists('attribute_set', $attributeSets)) { + foreach ($attributeSets['attribute_set'] as $attributeSet) { + $attributesData = array_key_exists(0, $attributeSet['attributes']['attribute']) + ? $attributeSet['attributes']['attribute'] : [$attributeSet['attributes']['attribute']]; + foreach ($attributesData as $attributeData) { + $values = []; + $optionsData = array_key_exists(0, $attributeData['options']['option']) + ? $attributeData['options']['option'] : [$attributeData['options']['option']]; + foreach ($optionsData as $optionData) { + $values[] = $optionData['label']; + } + $attributes[$attributeSet['name']][] = + ['name' => $attributeData['attribute_code'], 'values' => $values]; + } + } + } + return $attributes; + } + + /** + * @return array + */ + protected function getCategoriesAndWebsites() + { + /** @var \Magento\Store\Model\StoreManager $storeManager */ + $storeManager = $this->fixtureModel->getObjectManager()->create(\Magento\Store\Model\StoreManager::class); + /** @var $category \Magento\Catalog\Model\Category */ + $category = $this->fixtureModel->getObjectManager()->get(\Magento\Catalog\Model\Category::class); + + $result = []; + //Get all websites + $websites = $storeManager->getWebsites(); + foreach ($websites as $website) { + $websiteCode = $website->getCode(); + //Get all groups + $websiteGroups = $website->getGroups(); + foreach ($websiteGroups as $websiteGroup) { + $websiteGroupRootCategory = $websiteGroup->getRootCategoryId(); + $category->load($websiteGroupRootCategory); + $categoryResource = $category->getResource(); + //Get all categories + $resultsCategories = $categoryResource->getAllChildren($category); + foreach ($resultsCategories as $resultsCategory) { + $category->load($resultsCategory); + $structure = explode('/', $category->getPath()); + $pathSize = count($structure); + if ($pathSize > 1) { + $path = []; + for ($i = 0; $i < $pathSize; $i++) { + $path[] = $category->load($structure[$i])->getName(); + } + array_shift($path); + $resultsCategoryName = implode('/', $path); + } else { + $resultsCategoryName = $category->getName(); + } + //Deleted root categories + if (trim($resultsCategoryName) != '') { + $result[$resultsCategory] = [$websiteCode, $resultsCategoryName]; + } + } + } + } + return array_values($result); + } + + /** + * @return array + */ + protected function getSearchConfig() + { + if (!$this->searchConfig) { + $this->searchConfig = $this->fixtureModel->getValue('search_config', null); + } + return $this->searchConfig; + } + + /** + * @param string $name + * @return int|mixed + */ + protected function getSearchConfigValue($name) + { + return $this->getSearchConfig() === null + ? 0 : ($this->getSearchConfig()[$name] === null ? 0: $this->getSearchConfig()[$name]); + } + + /** + * @return array + */ + protected function getSearchTerms() + { + $searchTerms = $this->fixtureModel->getValue('search_terms', null); + if ($searchTerms !== null) { + $searchTerms = array_key_exists(0, $searchTerms['search_term']) + ? $searchTerms['search_term'] : [$searchTerms['search_term']]; + } + return $searchTerms; + } } diff --git a/setup/src/Magento/Setup/Fixtures/dictionary.csv b/setup/src/Magento/Setup/Fixtures/dictionary.csv new file mode 100644 index 0000000000000000000000000000000000000000..c839b7c46f51c25b1cda3afe4bfb7466e4ef082e --- /dev/null +++ b/setup/src/Magento/Setup/Fixtures/dictionary.csv @@ -0,0 +1,7373 @@ +the +of +and +a +in +to +it +is +to +was +I +for +that +you +he +be +with +on +by +at +have +are +not +this +but +had +they +his +from +she +that +which +or +we +an +were +as +do +been +their +has +would +there +what +will +all +if +can +her +said +who +one +so +up +as +them +some +when +could +him +into +its +then +two +out +time +my +about +did +your +now +me +no +other +only +just +more +these +also +people +know +any +first +see +very +new +may +well +should +like +than +how +get +way +one +our +made +got +after +think +between +many +years +er +those +go +being +because +down +yeah +three +good +back +make +such +on +there +through +year +over +must +still +even +take +too +more +here +own +come +last +does +oh +say +no +going +work +where +erm +us +government +same +man +might +day +yes +however +put +world +over +another +in +want +as +life +most +against +again +never +under +old +much +something +Mr +why +each +while +house +part +number +found +off +different +went +really +thought +came +used +children +always +four +where +without +give +few +within +about +system +local +place +great +during +although +small +before +look +next +when +case +end +things +social +most +find +group +quite +mean +five +party +every +company +women +says +important +took +much +men +information +both +national +often +seen +given +school +fact +money +told +away +high +point +night +state +business +second +British +need +taken +done +right +having +thing +looked +London +area +perhaps +head +water +right +family +long +hand +like +already +possible +nothing +yet +large +left +side +asked +set +whether +days +mm +home +called +John +development +week +use +country +power +later +almost +young +council +himself +far +both +use +room +together +tell +little +political +before +able +become +six +general +service +eyes +members +since +times +problem +anything +market +towards +court +public +others +face +full +doing +war +car +felt +police +keep +held +problems +road +probably +help +interest +available +law +best +form +looking +early +making +today +mother +saw +knew +education +work +actually +policy +ever +so +at least +office +am +research +feel +big +body +door +let +Britain +name +person +services +months +report +question +using +health +turned +lot +million +main +though +words +enough +child +less +book +period +until +several +sure +father +level +control +known +society +major +seemed +around +began +itself +themselves +minister +economic +wanted +upon +areas +after +therefore +woman +England +city +community +only +including +centre +gave +job +among +position +effect +likely +real +clear +staff +black +kind +read +provide +particular +became +line +moment +international +action +special +difficult +certain +particularly +either +open +management +taking +across +idea +further +whole +age +process +act +around +evidence +view +better +off +mind +sense +rather +seems +believe +morning +third +else +half +white +death +sometimes +thus +brought +getting +ten +shall +try +behind +heard +table +change +support +back +sort +Mrs +whose +industry +ago +free +care +order +century +range +European +gone +yesterday +training +working +ask +street +home +word +groups +history +central +all +study +usually +remember +trade +hundred +programme +food +committee +air +hours +experience +rate +hands +indeed +sir +language +land +result +course +someone +everything +certainly +based +team +section +leave +trying +coming +similar +once +minutes +authority +human +changes +little +cases +common +role +Europe +necessary +nature +class +reason +long +saying +town +show +subject +voice +companies +since +simply +especially +department +single +short +personal +pay +value +member +started +run +patients +paper +private +seven +UK +eight +systems +herself +practice +wife +price +type +seem +figure +former +lost +right +need +matter +decision +bank +countries +until +makes +union +terms +financial +needed +south +university +club +president +friend +parents +quality +cos +building +north +stage +meeting +foreign +soon +strong +situation +comes +late +bed +recent +date +low +US +concerned +girl +hard +American +David +according to +twenty +higher +tax +production +various +understand +led +bring +schools +ground +conditions +secretary +weeks +clearly +bad +art +start +include +poor +hospital +friends +decided +shown +music +month +English +tried +game +1990 +May +anyone +wrong +ways +chapter +followed +cost +play +present +love +issue +goes +described +award +Mr. +king +royal +results +workers +April +expected +amount +students +despite +knowledge +June +moved +news +light +March +approach +cut +basis +hair +required +further +paid +series +better +before +field +allowed +easy +kept +questions +natural +live +future +rest +project +greater +feet +meet +simple +died +for +happened +added +manager +computer +security +near +met +evening +means +round +carried +hear +bit +heart +forward +sent +above +attention +labour +story +structure +move +agreed +nine +letter +individual +force +studies +movement +account +per +call +board +success +1989 +French +following +considered +current +everyone +fire +agreement +please +boy +capital +stood +analysis +whatever +population +modern +theory +books +stop +legal +Scotland +material +son +received +model +chance +environment +finally +performance +sea +rights +growth +authorities +provided +nice +whom +produced +relationship +talk +turn +built +final +east +1991 +talking +fine +worked +west +parties +size +record +red +close +property +myself +example +space +giving +normal +nor +reached +buy +serious +quickly +Peter +along +plan +behaviour +France +recently +term +previous +couple +included +pounds +anyway +cup +treatment +energy +total +thank +director +prime +levels +significant +issues +sat +income +top +choice +costs +design +pressure +scheme +July +change +a bit +list +suddenly +continue +technology +hall +takes +ones +details +happy +consider +won +defence +following +parts +loss +industrial +activities +throughout +spent +outside +teachers +generally +opened +floor +round +activity +hope +points +association +nearly +United +allow +rates +sun +army +sorry +wall +hotel +forces +contract +dead +Paul +stay +reported +as well +hour +difference +meant +summer +county +specific +numbers +wide +appropriate +husband +top +played +relations +Dr +figures +chairman +set +lower +product +colour +ideas +George +St +look +arms +obviously +unless +produce +changed +season +developed +unit +appear +investment +test +basic +write +village +reasons +military +original +successful +garden +effects +aware +yourself +exactly +help +suppose +showed +style +employment +passed +appeared +page +hold +suggested +Germany +continued +October +offered +products +popular +science +New +window +expect +beyond +resources +rules +professional +announced +economy +picture +okay +needs +doctor +maybe +events +direct +gives +advice +running +circumstances +sales +risk +interests +September +dark +event +thousand +involved +written +park +1988 +returned +ensure +America +fish +wish +opportunity +commission +1992 +oil +sound +ready +lines +shop +looks +James +immediately +worth +college +press +January +fell +blood +goods +playing +carry +less +film +prices +useful +conference +operation +follows +extent +designed +application +station +television +access +Richard +response +degree +majority +effective +established +wrote +region +green +York +ah +western +traditional +easily +cold +shows +offer +though +statement +Scottish +published +forms +German +down +accept +miles +independent +election +support +importance +lady +site +jobs +needs +plans +earth +earlier +title +parliament +standards +leaving +interesting +houses +planning +considerable +girls +involved +Ireland +increase +species +stopped +concern +public +means +caused +raised +through +glass +physical +thought +Michael +eye +left +heavy +walked +daughter +existing +competition +speak +responsible +up to +river +follow +software +complete +above +November +December +purpose +mouth +medical +responsibility +Sunday +Wales +leader +tomorrow +piece +thirty +lay +officer +task +blue +answer +stand +thinking +extra +highly +places +arm +eventually +campaign +ability +appeal +whole +Charles +skills +opposition +remained +pattern +method +miss +hot +lead +source +bought +baby +lack +once +bill +division +remain +surface +older +charge +methods +trouble +fully +equipment +moving +suggest +disease +officers +past +peace +male +slightly +demand +failed +wants +attempt +types +Christmas +hit +post +policies +hardly +ii +arrived +compared +below +otherwise +windows +West +deal +directly +interested +sale +like +firm +status +happen +box +even if +teacher +radio +provision +variety +show +ran +sector +return +factors +essential +direction +beautiful +civil +base +waiting +caught +sit +develop +character +safety +placed +past +completely +tea +introduced +killed +love +mum +context +fifty +primary +animals +culture +Oxford +brother +obvious +weight +discussion +created +1987 +future +other +start +States +none +sold +let's +machine +afternoon +knows +environmental +fair +William +February +provides +wait +league +trees +positive +organisation +win +condition +families +argument +Saturday +learn +up +normally +claimed +truth +senior +kitchen +works +add +lived +library +minute +believed +enough +transport +share +principle +create +agree +born +players +cash +exchange +rule +budget +turn +pupils +nuclear +sitting +version +English +best +features +duty +annual +balance +front +send +boys +presence +protection +dog +courses +individuals +matters +media +avoid +influence +presented +speaker +stone +relevant +apply +August +explain +deep +Robert +1986 +achieved +slowly +relatively +shares +letters +finished +survey +huge +accepted +covered +review +Smith +closed +form +marriage +commercial +aid +lives +collection +living +speech +Africa +regional +differences +benefit +apparently +effort +gets +executive +later +latter +function +failure +return +chair +reference +horse +becomes +attack +reports +practical +queen +subjects +career +bar +official +text +appears +separate +student +names +sell +holiday +larger +cells +open +progress +early +states +helped +visit +smiled +stock +memory +merely +studio +key +putting +eat +opinion +understanding +regular +decisions +chief +drawn +firms +remains +facilities +values +district +cars +due +mhm +begin +managed +receive +corner +image +edge +sister +politics +expression +instead +impact +quarter +forced +inside +views +scale +plant +race +ball +gold +join +Henry +spend +voice +alone +additional +benefits +1985 +trust +for instance +largely +advantage +associated +increased +standing +dad +foot +somebody +pain +gas +clothes +smaller +aspects +active +affairs +possibly +increase +railway +ended +feeling +network +leaders +nevertheless +cause +half +powerful +step +complex +joined +plants +standard +holding +carefully +length +mind +rise +strength +crime +hard +wind +Mary +possibility +becoming +damage +records +reduce +examples +mainly +credit +winter +impossible +insurance +explained +units +currently +forest +formed +somewhere +earlier +beginning +regarded +fall +confidence +discussed +speed +legislation +mentioned +along +pulled +spoke +debate +intended +bodies +message +middle +plus +supply +100 +skin +Edward +stuff +providing +entirely +front +domestic +require +proved +expressed +treated +match +solution +previously +tonight +patient +actual +difficulties +farm +united +far +build +reach +proposals +extremely +choose +ministers +technical +fresh +ordinary +scene +materials +museum +Thomas +move +article +prevent +achieve +customers +includes +powers +band +items +justice +play +animal +internal +suggests +excellent +face +rich +assessment +save +phone +fairly +football +watched +telephone +steps +decide +South +traffic +watch +coffee +deal +sources +past +buildings +increasingly +relief +distance +introduction +forty +administration +no +safe +applied +sight +Mark +island +potential +banks +housing +meaning +existence +claim +northern +enjoy +reduced +twelve +equally +in front of +walk +very +apart from +watching +cultural +famous +latest +users +TV +cabinet +legs +institutions +Japan +measures +reality +proper +video +worse +lose +argued +train +spirit +programmes +accounts +trial +target +fear +joint +doubt +formal +unemployment +prison +accident +concept +limited +elements +strange +served +papers +discovered +conservative +rock +cover +usual +tree +smile +unable +warm +surely +organization +battle +proportion +difficulty +sides +refused +weekend +construction +picked +distribution +dinner +wine +while +works +obtained +exercise +writing +asking +showing +ahead +rural +lovely +applications +twice +factor +path +games +funds +whereas +nobody +shape +initial +substantial +referred +tend +seat +improve +onto +thanks +aircraft +light +contact +quiet +rain +background +identified +contrast +officials +strategy +average +master +forget +leading +soft +reasonable +seeing +pound +grounds +raise +immediate +communication +client +Paris +star +fourth +suitable +determined +ought +detail +everybody +noted +equal +imagine +appointed +manner +homes +classes +freedom +operations +detailed +keeping +selection +requirements +pair +draw +walls +talks +working +call +danger +attitude +user +overall +offer +female +relationships +Edinburgh +note +afraid +pick +charges +democratic +elections +entered +courts +growing +goal +straight +techniques +sufficient +middle +agency +scientific +eastern +crisis +rose +correct +removed +prince +theatre +Irish +laid +act +expensive +markets +sign +educational +capacity +telling +happens +absolutely +patterns +whilst +managers +purposes +employees +1984 +totally +opportunities +cause +break +will +procedure +feeling +output +mental +frequently +bridge +dangerous +either +fingers +recognition +largest +turning +arrangements +sites +profits +quick +absence +sentence +beside +pass +fields +critical +pointed +prove +listen +inc +recorded +cost +signed +hill +dropped +card +tour +understood +notes +track +1983 +partly +replaced +increased +weather +principles +seriously +familiar +related +package +elsewhere +teaching +bottom +necessarily +commitment +player +double +birds +properly +1993 +Jack +threat +notice +unlikely +admitted +1981 +replied +silence +route +file +liked +supported +issued +perfect +victory +discuss +widely +occur +second +violence +efforts +element +neck +carrying +conflict +pieces +Darlington +under +profit +reaction +colleagues +historical +standard +end +Friday +finance +hope +rooms +projects +closely +fund +daily +below +cell +Liverpool +Tom +southern +expenditure +increasing +discipline +completed +occurred +individual +spring +audience +lead +thousands +grow +conversation +tiny +congress +emphasis +finding +exist +check +alone +consideration +speaking +learning +defined +seek +1979 +appearance +maintain +option +dry +bright +urban +pictures +estate +debt +youth +neither +affected +married +feature +payment +exhibition +liberal +supposed +assembly +reform +empty +boat +suffered +bus +hell +remembered +driver +lunch +flowers +heat +processes +upper +volume +share +captain +murder +North +fifteen +represented +meetings +contribution +drugs +die +feelings +outside +Ian +arts +leg +serve +dealing +writing +curriculum +bag +sought +apparent +branch +beginning +noticed +procedures +models +Martin +enter +revealed +institute +establish +object +occasion +waste +facts +membership +requires +shook +Monday +claims +control +prepared +younger +faith +shops +challenge +answer +Russian +moral +pleasure +orders +Alan +heads +bloody +careful +filled +Corp +literature +birth +1980 +leading +code +centres +broke +prepared +that +professor +1982 +aye +wood +gentleman +flight +entry +pretty +attractive +wild +investigation +crown +protect +nodded +greatest +subject to +functions +encourage +belief +care +developments +description +tradition +Japanese +thin +adopted +vital +document +conclusion +hoped +Italy +enjoyed +engineering +coal +transfer +address +breath +along with +Ltd +alternative +total +schemes +copy +desire +search +effectively +organisations +demands +pushed +visit +etc +planning +farmers +ancient +released +opening +lips +iii +treaty +newspaper +aim +drug +identify +engine +Manchester +USA +tests +owner +sky +Tony +wearing +depends +elderly +ministry +Australia +busy +inside +anybody +reading +external +capable +marketing +streets +partner +respect +shot +institution +generation +acid +realised +chosen +wider +his +narrow +horses +broad +ordered +wonderful +key +contained +laughed +bringing +clients +typical +drink +Stephen +employed +atmosphere +slow +wondered +clean +actions +entire +troops +Leeds +vote +definition +welfare +reduction +row +walking +laws +visitors +release +meanwhile +confirmed +examination +doors +leadership +attitudes +East +enable +beneath +journey +milk +stated +hence +IBM +machines +affect +grey +screen +criticism +surprise +reading +nineteen +stories +billion +constant +teeth +brain +explanation +brief +signs +married +highest +cover +starting +knowing +claim +creation +castle +governments +goals +intention +India +vast +flat +guide +drive +surprised +easier +ideal +shut +readers +run +Bill +magazine +bound +terrible +thoughts +kinds +academic +worry +minor +seats +customer +significance +measure +pleased +unfortunately +o'clock +revolution +attempts +noise +charged +rare +biggest +rather than +somewhat +sections +stared +seeking +paying +meeting +encouraged +thick +Jones +loved +metal +grand +plenty +note +phase +coast +injury +China +granted +motion +observed +technique +ill +drew +potential +factory +lying +severe +mine +lights +wonder +Harry +spread +contains +strongly +offers +afterwards +committed +tape +shoulder +bear +corporate +obtain +kill +that is +worst +learned +settlement +ooh +grew +represent +rapidly +tall +hole +living +adult +iron +amongst +faced +negative +afford +lots +index +permanent +beat +trip +contain +fundamental +doctors +desk +ourselves +sport +unions +implications +fashion +content +similarly +elected +proposed +judge +pool +inflation +brown +Brian +originally +funny +via +practices +somehow +payments +odd +Andrew +pension +pay +crucial +fit +inner +appointment +used +flow +launched +Chris +independence +Spain +objects +setting +little +least +colours +palace +perfectly +combination +contracts +criminal +consequences +pages +contemporary +UN +talked +session +sharp +structures +planned +drive +Wednesday +Kingdom +falling +sample +virtually +fast +sick +movements +dogs +Anne +Yorkshire +Roman +accommodation +nation +temperature +massive +societies +consumer +cities +offices +documents +valley +indicated +breakfast +stayed +kids +display +named +bedroom +sports +aspect +unique +Steve +sixty +author +lane +objectives +secondary +wear +republic +agent +interpretation +assistance +directors +badly +alright +parliamentary +African +Joe +unknown +industries +assets +selling +moreover +Northern +nose +Jim +Russia +subsequent +place +describe +declared +gallery +allowing +ship +other than +visited +cross +grown +crowd +recognised +interview +broken +Simon +argue +BBC +naturally +thinking +general +Mike +meal +catch +representatives +proceedings +tears +alive +involving +shoulders +employers +begun +departments +vision +yours +unix +beauty +guilty +proposal +impression +square +angry +regulations +regions +vehicle +Jane +democracy +sequence +offering +Graham +enormous +invited +cancer +sheet +struck +Glasgow +rarely +involve +involvement +improvement +ninety +motor +shock +tone +significantly +contact +manufacturing +close +seconds +dress +assumed +well +quietly +grass +nations +provisions +communities +roof +yellow +indicate +distinction +present +statements +comments +allows +late +pollution +fruit +acting +involves +unusual +assume +towns +lucky +fuel +spot +properties +touch +fall +Major +bottle +except +anywhere +net +buying +long-term +soil +sum +stages +decline +missed +reader +extensive +manage +calling +talk +heavily +containing +plate +advertising +revenue +remaining +glad +diet +agricultural +artist +plastic +artists +gently +Bob +alright +location +ring +ice +operate +lies +candidates +Italian +pull +passage +principal +cope +linked +tired +periods +firmly +occasionally +identity +persons +limited +warned +efficient +runs +hundreds +maintenance +divided +unlike +establishment +channel +producing +fight +happening +song +map +expert +formation +comfortable +border +constitution +weapons +emergency +Chinese +waited +continues +arranged +link +Wilson +spokesman +extended +rail +Philip +candidate +believes +funding +promised +positions +mostly +household +remove +performed +cat +sleep +abroad +teams +mountain +program +countryside +stars +victim +studied +relative +criteria +conventional +parish +framework +willing +strike +cheap +ref +sudden +approval +concentration +partners +autumn +maintained +warning +cards +roads +approved +lake +starts +determine +liability +editor +realise +thinks +helping +longer +proposed +voluntary +settled +grant +characters +valuable +situations +deputy +walk +regularly +occasions +trading +rejected +agriculture +premises +dramatic +fill +theme +silver +golden +duties +friendly +arguments +accused +driving +losses +error +reflected +dream +shortly +wealth +working +temporary +federal +stress +painting +request +initially +reflect +lifted +eighty +hello +pub +recovery +loan +electricity +1980s +chest +Margaret +refer +taught +silent +Brown +beach +Indian +eleven +answered +learning +recession +focus +facing +video-taped +height +clubs +item +characteristics +emerged +options +matter +hurt +forgotten +worried +bread +admit +chief +specifically +owners +Lewis +statutory +mirror +agents +writer +deeply +Welsh +foundation +struggle +1978 +parent +dependent +mistake +reputation +Frank +eggs +decade +steel +gain +leads +publication +resistance +offence +incident +Thursday +prefer +stations +denied +examine +lifespan +wages +tasks +gained +acquired +outcome +claims +travel +competitive +marked +panel +resolution +wished +dear +efficiency +demanded +flat +yards +subsequently +gradually +businesses +chancellor +chain +specialist +1977 +dressed +tells +negotiations +relating +supporters +armed +radical +sleep +representation +agencies +theories +outside +shoes +threatened +spending +keen +drove +gardens +acceptable +notion +initiative +stairs +Cambridge +advance +leaves +recognise +worker +essentially +empire +shared +sensitive +uses +clause +attached +Taylor +living +fallen +Belfast +fighting +dear +controlled +sugar +Elizabeth +block +global +delivery +changing +Lee +computers +ages +meat +mass +emotional +brothers +bird +expansion +islands +healthy +Middlesbrough +aside +attend +secret +store +1976 +break +writers +self +rising +travel +bigger +alternative +rapid +instructions +wet +adequate +weak +licence +fixed +soldiers +examined +aimed +owned +average +awareness +centuries +images +drama +notice +Tuesday +handed +furniture +gate +scientists +administrative +sees +pocket +wooden +uncle +remarkable +co-operation +creating +Adam +Luke +newspapers +currency +comprehensive +intelligence +charity +fifth +links +hoping +respond +surprising +extension +solid +survive +growing +apart +restaurant +churches +precisely +pale +skill +close +connection +mass +dealt +brilliant +maximum +losing +depend +200 +experienced +across +introduce +philosophy +convention +gun +films +sons +eh +communications +regime +miss +attended +suffer +copies +councils +round +partnership +inquiry +Sarah +residents +absolute +firm +French +corporation +arrested +reports +minority +arrival +in addition to +listening +taste +sad +gap +plane +scope +experiences +coat +command +consequence +left +fun +Birmingham +tory +golf +electronic +behind +visual +retirement +replace +rise +darkness +fault +directed +complete +1970s +enemy +comment +electric +priority +metres +database +Tim +pure +Spanish +Nigel +rough +core +circle +result +literary +bay +championship +guests +1975 +insisted +mere +bits +successfully +limit +imposed +continuing +Rome +sounds +abuse +categories +languages +tower +Thatcher +anger +accompanied +category +collected +present +need +comparison +supreme +supplied +chemical +fans +greatly +sweet +wedding +teaching +represents +duke +mark +personnel +genuine +adults +mail +politicians +occurs +exciting +written +returning +promotion +longer +preparation +defendant +presumably +DNA +derived +Washington +paintings +fitted +mothers +affair +stupid +cricket +advanced +tank +arise +photographs +point +disappeared +expectations +findings +illness +citizens +mood +faces +tension +Commons +ladies +briefly +stones +mixture +classical +arrangement +extreme +Williams +nineteenth +discover +favourite +shot +causing +yard +begins +socialist +judgment +landscape +fail +feels +consumption +mill +1974 +informed +birthday +widespread +consent +confident +acts +Gloucester +sake +estimated +requirement +catholic +experts +Israel +numerous +throat +permission +ignored +guidance +moments +brings +costs +module +opposite +respectively +altogether +input +presentation +everywhere +distinct +statistics +repeated +tough +earnings +saved +finger +branches +fishing +components +truly +drink +turns +luck +boss +exists +champion +MP +answers +tools +cycle +recommended +intervention +mile +Scott +whenever +AIDS +promote +helpful +prospect +definitely +Johnson +organised +controls +Ben +express +Iraq +nurse +frame +perform +cottage +wave +adding +proud +by +winner +solicitor +neighbours +pilot +calls +mad +alliance +given +straight +survival +winning +votes +primarily +attacks +compensation +Sam +destroyed +camp +hat +territory +symptoms +prior to +ownership +wage +concluded +discussions +developing +cream +achievement +drawing +entrance +basically +poverty +disabled +extend +fast +suggestion +consistent +shareholders +degrees +mention +anxious +fewer +delivered +dark +transferred +employee +throw +assumption +inevitably +nervous +profession +awful +cool +hang +threw +vehicles +stable +realized +suit +hills +prize +drop +constitutional +perspective +Neil +satisfied +bid +aunt +festival +constantly +conscious +developing +connected +concerning +savings +reasonably +concentrate +pace +novel +operating +breach +purchase +crossed +Asia +chances +depth +calls +strategic +thrown +bills +Jean +Ken +reply +ha +Ruth +treat +green +sheep +dominant +phrase +push +eating +now that +conducted +employer +ears +contents +touched +prepare +sounded +Moscow +theoretical +setting +soul +wore +study +1960s +outstanding +benefit +vary +jacket +except +holy +processing +sand +clinical +prisoners +dispute +shadow +minimum +organizations +plaintiff +snow +cried +fit +driven +Joseph +port +recognized +servants +limits +pink +et al +sound +hearing +measured +dance +eighteen +sorts +Patrick +trained +stomach +slight +fought +points +storage +breaking +impressive +honest +provided +dismissed +glanced +related +cast +crew +defeat +hold +gift +enthusiasm +princess +press +spending +advantages +reaching +articles +resulted +files +hung +cuts +residential +extraordinary +visible +shouted +reducing +experiments +tables +finish +tendency +conduct +objective +report +ring +hospitals +mechanism +seventy +exception +poetry +inspector +1973 +covering +stepped +accurate +percent +victims +approached +distant +alongside +airport +furthermore +considerably +pressed +missing +origin +salt +personality +fight +Canada +arrive +fly +Greek +receiving +still +rocks +fees +fee +complicated +ends +musical +stands +moon +chamber +puts +turnover +attacked +ultimately +routine +observation +precise +plain +gentle +watch +staring +since +leisure +economics +device +broken +mortgage +live +leaves +confusion +cut +finds +instruments +secondly +Bush +certificate +ear +remote +satisfaction +responsibilities +ratio +coach +fears +sentences +holder +shopping +possession +selected +Bristol +sets +smoke +rugby +songs +clock +summary +implementation +protein +housing +escape +wing +fixed +helps +poll +Arthur +scored +chose +column +holidays +contributions +architecture +Nick +approximately +disaster +minds +Keith +marks +trust +neither +monetary +mountains +White +discovery +collect +spoken +steam +smooth +silly +childhood +teach +Kong +unity +staying +climate +percentage +villages +attracted +Americans +designs +secure +cutting +iv +Hong +last +taxes +tennis +peak +relation +readily +evaluation +frequency +Andy +shirt +cake +nights +bishop +test +pretty +Charlie +deliberately +round +determination +applies +automatically +standing +rent +psychological +violent +medicine +boards +protest +Anna +unemployed +final +being +Rose +reforms +inevitable +junior +signal +building +till +welcome +fat +roles +headquarters +sensible +visits +manufacturers +restrictions +samples +Pacific +improved +Berlin +grateful +strategies +score +tended +expense +loans +addressed +mode +structural +dozen +pride +newly +founded +variation +aged +rely +investors +infection +dominated +combined +survived +Helen +string +Lucy +experiment +fourteen +undertaken +committees +buyer +agreements +participation +welcome +match +complaints +ships +critics +guitar +camera +laboratory +waves +landlord +rang +hate +demonstrated +bomb +engaged +knife +so-called +modules +pleasant +headed +surgery +universities +millions +concepts +proof +marry +Maggie +operating +pressures +lives +sixteen +repeat +host +Dave +pitch +attempted +assess +penalty +tail +boxes +holes +deep +contribute +passing +reveal +cleared +thereby +acceptance +1972 +anxiety +above +Newcastle +formula +personally +Howard +mission +deaf +relatives +imagination +apple +dirty +rid +abandoned +appreciate +continuous +describes +suffering +circuit +stronger +responses +excitement +approaches +supply +plan +Zealand +tested +disk +holds +replacement +instrument +universe +memories +overseas +expertise +causes +solicitors +comfort +sergeant +trend +treasury +entitled +sounds +acquisition +opening +tickets +bath +delighted +sending +increasing +confirm +loose +state +targets +occasional +paragraph +writes +evident +Kent +desperate +handle +fellow +blind +occupied +overcome +dust +burden +psychology +relate +drivers +surprisingly +1970 +Matthew +cheese +consciousness +considering +universal +Gordon +aha +square +succeeded +knees +1971 +boots +smell +closer +mummy +slipped +component +regulation +Roger +attempt +locked +keeps +wheel +classic +MPs +cattle +consists +touch +illustrated +platform +shift +draft +purely +load +influenced +passengers +recommendations +preparing +solutions +Alexander +injuries +tenant +commonly +Victoria +leather +gathered +zone +sufficiently +like +Laura +squad +recall +steady +retain +checked +existed +attract +conservation +flesh +pack +publicity +rose +mean +variations +split +sixth +edition +concerns +tied +summit +engineers +Terry +listed +judges +researchers +equivalent +assist +upstairs +hers +Francis +located +nurses +fear +mark +suggesting +whispered +serving +intellectual +influence +stream +generated +consequently +San +authors +wondering +judgement +experience +Victorian +egg +supplies +level +produces +councillor +roots +taxation +bathroom +ultimate +awarded +stick +glasses +raising +qualities +layer +lost +creative +medieval +risks +assumptions +displayed +dreams +Germans +accounting +curve +drawing +backed +adopt +colleges +guard +evolution +sign +aims +sharply +constructed +advised +softly +settle +decades +completion +linguistic +ignore +convinced +Colin +judicial +photograph +sophisticated +Alice +asleep +paused +amounts +poem +recording +carbon +Durham +possibilities +good +explains +equation +NHS +vulnerable +raw +net +deaths +babies +illegal +outer +topic +medium +till +promise +tends +hopes +angle +interviews +ban +feed +potentially +machinery +tongue +coalition +travelling +define +consultation +reception +pulling +Nicholas +integration +revolutionary +quoted +compare +surrounded +bitter +attempting +guess +improvements +climbed +cathedral +heaven +wanting +painted +Australian +changing +grammar +jumped +Ulster +Gulf +native +imperial +persuade +voices +conclusions +laughing +lift +1968 +Gary +boundaries +favour +guy +studying +Jimmy +beliefs +undoubtedly +wings +retained +joy +bone +informal +demonstrate +Douglas +regard +clear +calculated +flexible +meals +announcement +lawyers +ruled +account +sheets +tunnel +exercise +bars +carpet +quantity +catalogue +reminded +shrugged +notably +Anthony +schedule +petrol +investigate +hotels +buried +once more +journal +nowhere +considerations +concentrated +collective +destruction +frequent +versions +offences +agenda +clever +experimental +plays +Kate +listened +La +texts +plates +deficit +transition +Norman +spiritual +intense +indication +flew +pushing +rational +hanging +entitled +excluded +knocked +professionals +tight +composition +indicates +conservatives +Kevin +interaction +ceiling +guidelines +cold +roughly +Governor +qualifications +ethnic +me +argues +Dublin +inches +opera +pupil +cheaper +generous +prominent +inadequate +accordingly +welcomed +instruction +logical +passion +drawings +exposure +departure +blame +racing +mixed +historic +guest +Mrs. +pipe +modest +Dutch +lessons +hero +Lloyd +sectors +Diana +barely +logic +Essex +acute +harm +representing +discourse +voted +electrical +hearing +consumers +jury +Grant +weekly +acted +delay +valid +wherever +representative +transaction +bowl +increases +contributed +Christopher +record +leaned +lesson +lit +admission +stores +awards +automatic +timber +trousers +vote +habit +Oliver +arrange +red +matches +punishment +bones +cross +deny +rubbish +hide +mortality +complex +pc +earl +explore +urged +occupation +storm +darling +keys +customs +profile +gross +depression +classroom +glance +mystery +mutual +reliable +wholly +entering +bare +liable +facility +stressed +stuck +realize +engineer +smiling +confined +province +registration +males +laughter +humour +resource +multiple +Albert +ruling +silk +waters +Rachel +paint +cotton +Atlantic +identification +claiming +sole +coverage +arising +Owen +honour +poet +prospects +travelled +divisions +posts +avoided +in case +charter +managing +pregnant +obligation +win +adds +formally +flying +Latin +nearby +Egypt +exact +directions +curious +bother +participants +lawyer +resignation +bearing +sets +pointing +tool +damages +speakers +fate +daddy +devices +phenomenon +strain +substance +bags +wire +Wood +underlying +responded +enjoying +visitor +joining +uncertainty +but +drop +submitted +flower +Ford +California +perception +identical +farming +letting +audit +satisfactory +Billy +ticket +lists +preference +Great +thirteen +Van +secret +pop +album +federation +learnt +deliver +Westminster +chemicals +farmer +variables +male +assault +marginal +leave +namely +fed +distinctive +kingdom +assessed +refuse +electoral +urgent +allowance +observations +libraries +Lawrence +reflects +force +sympathy +running +falls +publishing +recovered +stability +canal +funeral +singing +titles +beds +sessions +restricted +Sheffield +Nottingham +expecting +clothing +drinks +disposal +failing +joke +focus +succeed +Maria +typically +official +conversion +presidential +generations +mayor +sharing +Clare +worth +transactions +era +policeman +Fred +gaze +controversial +count +proceed +Young +folk +fabric +oral +horror +Kelly +everyday +emperor +viewed +sing +belt +fortune +demand +doubt +crash +encouraging +interpreted +Louis +organic +maintaining +removal +female +routes +continued +trials +enables +print +laugh +bent +expected +connections +magistrates +errors +statistical +resolved +desirable +recognize +Stuart +thoroughly +injured +van +blocks +prosecution +register +trends +preferred +reckon +innocent +ideology +belong +improved +past +corridor +exclusive +tale +pairs +prayer +collapse +lease +talent +gains +separated +marked +experienced +persuaded +sighed +butter +suggestions +Russell +unexpected +foods +picking +banking +sciences +superb +contacts +operated +alarm +go +Poland +gene +daughters +sheer +guardian +count +cloud +disappointed +Bernard +format +scenes +frightened +hardware +traditionally +gastric +genes +effectiveness +full-time +intend +concentrations +defend +strict +fighting +creatures +closer +Swindon +capitalist +Walker +addition +chocolate +emerge +Hugh +hidden +likes +Susan +Stewart +reactions +lands +establishing +swept +anniversary +permitted +export +1967 +justify +tissue +Davies +bet +specified +romantic +garage +conviction +declined +resigned +Clarke +advise +scientist +root +asset +warmth +bulk +bands +knee +minimum +humans +references +any +associations +muscles +withdrawal +registered +distributed +regarding +exposed +declaration +graphics +reluctant +actor +switched +sisters +winners +eighteenth +chemistry +rest +justified +stop +converted +boundary +suspect +magnificent +stretched +convenient +friendship +established +recover +destroy +Jackson +mess +correspondent +navy +dollar +craft +reflection +chicken +plans +tin +Miller +curtains +gesture +tourist +diary +protected +ocean +discussing +practitioners +bloody +entertainment +nearest +mechanisms +closed +expenses +uncertain +artificial +democrats +damaged +composed +heating +diplomatic +drinking +discrimination +rows +bench +councillors +acquire +installed +guns +killing +Microsoft +blow +salary +Baker +tip +1950s +physically +estates +tremendous +marine +ease +institutional +mechanical +retail +resist +mixed +literally +chapel +distinguish +wildlife +Rivers +Iran +tories +doubts +formerly +priorities +reserves +publications +commented +gender +passenger +Sussex +strictly +boats +causes +pen +chapters +cheque +required +testing +carriage +weapon +generate +Clinton +asks +earn +supporting +mentally +judge +messages +females +biological +applying +implies +known +Emily +rolled +tube +functional +accidents +flexibility +chairs +Phil +styles +cap +straightforward +moves +wise +fired +organized +inspection +Derek +mathematics +heritage +superior +1969 +specially +finance +cloth +sociology +desperately +fiction +equity +satisfy +Lords +shell +Wright +lad +whereby +forests +suit +pursue +digital +increases +tenants +refers +voters +piano +productivity +part-time +lightly +assistant +Commander +address +situated +restoration +outlined +imports +comment +stolen +Harris +clerk +cinema +Ann +covers +capitalism +spectacular +shapes +controversy +Marx +gates +escaped +Robin +continuing +trains +ensuring +colonel +confused +grants +remarks +bonds +wives +computing +constraints +solve +aggressive +availability +unfair +sadly +invasion +tracks +compete +closure +spare +painful +earned +venture +topics +wonder +equivalent +grade +Korea +pot +emotions +washed +escape +abstract +Eric +murmured +stake +lift +states +breeding +securities +asian +mud +Joan +estimates +cheek +stored +correctly +refugees +Moore +obligations +spirits +unhappy +Ross +networks +beaten +snapped +initiatives +understanding +alter +shame +pensions +oxygen +therapy +associated +courage +discretion +dates +deposits +hopefully +exports +legislative +Eliot +ward +monthly +deciding +describing +assuming +opposed +Alex +searching +intelligent +impose +explicit +jurisdiction +designer +tie +fellow +quantities +fleet +Barry +seller +RAF +borough +stand +flats +virtue +constituency +complained +coloured +midnight +taxi +engines +railways +display +just +ridiculous +Caroline +debts +comparable +amazing +acknowledged +appeal +wars +successive +refusal +incorporated +creature +secured +economies +isolation +Leicester +succession +signals +working-class +physics +feared +concert +tonnes +realistic +hungry +launch +Evans +resort +burst +sort +back +Walter +gear +Shakespeare +surveys +volunteers +stick +separation +la +demonstration +fails +conception +decent +discount +unnecessary +prevented +flying +worn +dictionary +twentieth +fat +random +retired +local +origins +packed +achieving +heading +forever +influential +masters +channels +harbour +producers +duration +Thames +cable +1945 +desert +terrace +assured +allocation +check +diseases +merchant +constable +Vietnam +Dean +recalled +lifetime +chips +Ray +genetic +complaint +near +visiting +explaining +order +marvellous +Malcolm +Morgan +restored +earliest +enabled +release +Cardiff +assurance +bottles +brick +essence +autonomy +giant +requiring +hunting +consensus +differ +vegetables +junction +workshop +measure +purchaser +secure +attendance +necessity +bottom +demanding +skilled +shaking +subtle +select +attack +questioned +sooner +producer +planet +elegant +amendment +hopes +carries +recommend +lesser +farms +parallel +limitations +locally +Marie +tragedy +instance +cousin +collections +backwards +grain +resulting +fraud +swung +landed +quarters +liberation +seventeen +referring +interior +bike +suspended +officially +journalists +nasty +movie +suppliers +dealer +shows +soldier +intensive +kit +witness +delight +symbol +forum +casual +tropical +shorter +Allen +crimes +printed +miners +feeding +relax +pass +manufacturer +chip +crazy +forming +kissed +swimming +happily +copper +arguing +shots +landing +nursery +entries +preliminary +besides +arises +partial +households +damp +wool +1964 +servant +Pakistan +attending +Guy +plot +muscle +beings +inch +simultaneously +concrete +Roy +roll +bell +neighbour +reign +analysed +tide +expand +alleged +guilt +rank +introducing +transfer +uses +ceremony +Morris +separately +opinions +enquiry +grinned +lover +slept +choices +assistant +severely +finest +poured +vertical +Easter +upset +hey +allegations +IRA +justification +detective +programs +throwing +strike +ate +appendix +Jenny +districts +commonwealth +dealers +delicate +forms +advisers +lonely +dull +mouse +Pat +occupational +pity +behave +complexity +youngsters +riding +weakness +excessive +Clark +progressive +captured +stance +undertake +exceptional +faster +Iraqi +remind +counter +Greece +triumph +remarked +continental +striking +integrated +pit +encountered +implemented +sizes +directive +participate +safely +lowest +lighting +villa +okay +downstairs +portrait +alternatively +edges +focused +bye +residence +panic +label +aims +magazines +neat +combined +transformation +theft +lecture +incidence +scores +radiation +perceived +spread +firstly +interface +doctrine +shouting +affecting +ours +excuse +accepting +risen +Lancashire +approach +deposit +pond +substantially +innovation +diagnosis +gifts +allocated +regard +remainder +speculation +approaching +dialogue +estimate +wash +supervision +dying +exclusively +happiness +politically +timing +chronic +Geoffrey +peasants +tightly +characteristic +accuracy +compulsory +wrapped +interim +objective +Benjamin +walking +infant +Bruce +judged +splendid +ride +divorce +magic +Cleveland +bond +review +short-term +ambulance +brave +investigations +systematic +Green +seized +cry +laugh +advanced +obliged +opens +eaten +relevance +1930s +careers +Liz +withdrawn +Barbara +no +payable +handsome +fun +Ms +instances +governors +horrible +measurement +employ +primitive +steadily +switch +fascinating +Brazil +ideological +pile +mounted +metropolitan +alternatives +dollars +north-east +explosion +starting +glory +scarcely +Harriet +surrounding +coup +domain +fence +threatening +dragged +breast +habits +mine +hierarchy +grip +socialism +enquiries +particles +Sweden +choosing +colleague +monitoring +Midlands +restore +printer +imagined +doorway +prisoner +juice +classification +estimated +equilibrium +solely +with regard to +serves +peaceful +observer +explanations +circles +rescue +maps +hated +observe +Hughes +premier +mate +hypothesis +1966 +ride +companion +liver +factories +buyers +reward +controlling +satellite +loyalty +operational +pardon +improving +jump +potatoes +intervals +technological +near +fortunately +hostile +advisory +cook +precious +opponents +peasant +insist +geography +button +consistently +cultures +seeds +monopoly +accessible +tournament +moves +excited +determined +owed +pockets +belonged +Hollywood +dining +switch +traditions +compromise +intensity +chaos +obtaining +Mexico +King +combine +altered +nonsense +clouds +themes +suspicion +ranks +disorder +stocks +Kuwait +1965 +2000 +consultant +collapsed +purchased +impressed +half +Catherine +provincial +sterling +performances +instantly +Bell +constitute +arrest +dose +exercises +issue +competitors +spectrum +dangers +allies +travellers +plc +kid +disc +Donald +nowadays +Surrey +cheeks +endless +isolated +dimension +twin +bedrooms +clean +columns +privilege +post-war +volumes +broadcasting +commerce +historians +train +geographical +oak +actors +step +like +dynamic +freely +checking +equipped +inspired +density +1994 +forthcoming +HIV +boring +handled +poems +recording +unfortunate +banned +Karen +own +suspected +boom +tribunal +kicked +possessed +Jonathan +broadly +publicly +attributed +definite +challenged +extending +cooking +pause +strip +predicted +super +barrier +pregnancy +loud +menu +preserved +Avenue +restaurants +acres +prompted +senses +essay +lip +recruitment +defendants +presents +guarantee +invest +cats +maximum +notable +upwards +arose +cry +fierce +detected +indirect +German +witnesses +patch +sensitivity +Le +mistakes +receiver +crops +chin +wheels +rice +Dec +forgot +illustrate +reveals +Freud +limit +chap +Campbell +races +awkward +Turkey +implied +climb +widow +varied +slid +stopping +rope +steep +neutral +Oxfordshire +finish +debut +seed +challenge +promoted +delegation +hitherto +artistic +muttered +adoption +architect +dear +Kenneth +portfolio +continent +transformed +couples +probability +content +Robinson +struggling +mild +counties +wish +mention +fitness +tackle +dish +statute +invariably +Charlotte +prey +view +consultants +gather +arriving +corners +delegates +Holland +archbishop +Sue +withdraw +replacing +Milton +meaning +mature +differently +chart +technologies +woods +possess +cab +grace +toilet +grabbed +prevention +equality +wishes +bases +operator +regardless +harsh +colonial +ambitious +exploration +lords +investigated +collecting +Switzerland +shadows +Corbett +evil +Johnny +dramatically +Marshall +indicating +orchestra +lock +inhabitants +defeated +disappointment +magnetic +washing +fibre +correspondence +verbal +legitimate +requested +emotion +odds +workforce +vessels +brass +pursued +ph +balls +adviser +faint +handling +appointments +grandfather +motivation +sympathetic +publishers +peoples +socially +investments +rhythm +variable +Chelsea +memorial +well-known +empirical +roses +ceased +fluid +descriptions +incidents +DC +dismissal +appreciated +communicate +rushed +bronze +wisdom +Daniel +supper +adventure +tribute +seeks +promise +head +ye +1960 +crop +beef +suited +exercised +respects +terror +circulation +identifying +achievements +fool +intentions +proportions +lads +directory +Brighton +inn +promoting +flag +separate +Roberts +Ward +Dennis +clay +Cook +Norway +attraction +ends +disability +championships +vague +virus +shift +ranging +competence +examining +inform +spaces +goodness +gang +favourite +preserve +remembering +naval +molecules +hearts +trapped +actively +leaf +Brussels +distress +resolve +custody +packages +drinking +operators +myth +gain +voting +Mick +returns +tourists +encouragement +lacking +seldom +processor +sums +integrity +acknowledge +shortage +depressed +rightly +Louise +remarkably +repair +shoot +electronics +wishing +Kinnock +imprisonment +kings +waved +shared +shocked +uniform +added +reject +implement +pays +hesitated +seventh +magic +mid +populations +worthwhile +filling +crystal +fraction +qualified +Newton +Sally +server +Nato +specimens +kiss +reflecting +shower +missing +roll +sword +varieties +clinic +imply +ie +rivals +Julia +breakdown +Anderson +scales +fan +operates +blank +whoever +scandal +oldest +smart +favourable +filter +interviewed +absent +mining +gentlemen +enemies +champions +Duncan +exclusion +boot +locations +Hamilton +transmission +custom +tanks +tries +Gloucestershire +publisher +beating +evidently +Netherlands +Polish +lively +exceptions +Emma +appeals +Israeli +mobility +reviewed +buses +conclude +mix +shore +commissioner +absorbed +Norwich +dawn +developed +guards +incomes +parking +vendor +wishes +republics +loads +barriers +translation +evenings +Hungary +lectures +stimulus +conflicts +remains +margin +question +bothered +neighbourhood +tourism +meanings +FA +desktop +reportedly +risk +zero +demonstrations +dividend +opponent +wake +stiff +rejection +flavour +relates +borrow +emissions +representative +Midland +thereafter +enthusiastic +observers +cited +quid +fortnight +dreadful +guarantee +reduced +rigid +killer +ending +trick +successor +execution +influences +temperatures +mines +drank +coastal +greeted +nightmare +peculiar +corruption +tray +speaks +cupboard +creates +Jordan +Aberdeen +harder +burned +appearing +Swiss +rabbit +environments +comedy +referendum +bureau +avoiding +just about +matrix +honestly +profound +journalist +extended +Julie +tapes +suspension +delayed +eager +comply +selected +skirt +matched +feminist +Davis +Canadian +closing +acts +grief +relaxed +insight +deck +sensation +placing +sequences +temple +parks +tactics +verdict +adapted +enhance +corresponding +strings +accurately +running +pray +accent +envelope +interference +grandmother +examinations +phone +planned +shelf +deemed +waist +waste +onwards +applicable +futures +sauce +immense +purchase +breathing +allied +Norfolk +contest +expects +supports +km +blacks +decision-making +coins +genuinely +accounted +expressing +assessing +dance +scheduled +adjustment +charge +winds +meets +practically +merger +comparative +permit +celebrate +vessel +belonging +affection +outline +albeit +Lily +leaning +lounge +raises +Cheltenham +workshops +refusing +shallow +dishes +monitor +propose +blamed +dioxide +kind +broader +handling +bastard +uncomfortable +affects +proposition +representations +conservation +ya +makers +Yugoslavia +Fox +citizen +forcing +productive +woke +bored +beneficial +slip +campaigns +handful +aged +collar +curtain +diversity +hint +Thompson +disappear +charming +bonus +secrets +interrupted +specialists +accommodate +frustration +recommendation +meantime +coffin +daily +condemned +minimal +mobile +academy +testing +independently +appealed +museums +cruel +faces +murdered +on board +Turkish +aim +Will +territories +pressing +Churchill +commit +verse +research +orange +interval +threats +passive +suspicious +forgive +liberty +ghost +rear +believing +correlation +measurements +1963 +investigating +shade +layers +bias +overwhelming +certainty +Sunderland +cow +commissioned +trusts +maturity +resulting +fatal +surrounding +crying +planted +symbolic +isle +historian +enabling +removing +slope +excuse +angel +nearby +rats +straw +1962 +surfaces +gods +foundations +honours +Belgium +disputes +insects +inspiration +draw +presenting +registered +pavement +telephone +reserve +keeper +dimensions +predict +neighbouring +validity +breeze +ugly +expanded +lasted +irrelevant +complain +shelter +patient +driving +wealthy +upset +hostility +profitable +rod +fled +compact +lamp +shifted +supplier +crossing +phenomena +IT +measuring +horizon +rival +making +clergy +marble +pensioners +fragments +loyal +Alison +Stanley +conscience +sixties +Hill +saving +tune +moderate +1961 +soup +paths +struggled +popularity +score +singer +distinguished +climbing +kick +Betty +characteristic +interior +episode +oven +basket +noble +forwards +consisted +crowds +positively +pole +burning +pet +insufficient +evil +mysterious +jet +eligible +behalf +passes +nails +collaboration +lorry +nest +varying +enforcement +Spencer +Denmark +make-up +molecular +managerial +raid +ambition +middle-class +brand +migration +embassy +neatly +looks +worship +Olympic +devised +exclude +organ +favoured +linear +Samuel +cared +manor +detect +interpret +Kennedy +substances +crude +fantasy +counselling +abilities +treating +blew +embarrassment +executed +implication +Ron +printed +prospective +importantly +Preston +continually +Barnes +executives +catching +forehead +Ali +diverse +parental +elaborate +furious +definitions +appreciation +fiscal +Kim +commitments +sculpture +runs +striker +beans +brush +soccer +spell +reductions +contrary +soap +dated +stretch +publish +russians +pig +stroke +ladder +Greater +burning +expressions +useless +nerve +pence +Gabriel +rumours +relied +Edwards +semantic +inherent +embarrassed +1948 +specification +despair +yep +name +serum +Maxwell +Dick +apartment +Vienna +deliberate +stranger +philosophical +criterion +trap +pubs +utterly +link +frowned +awake +bureaucracy +nonetheless +sunshine +bloke +partially +remedy +battery +variable +within +forth +barn +ties +settlements +installation +crashed +negotiate +Somerset +nursing +dignity +promising +minus +criticised +sacred +analyse +senate +incentive +unpleasant +varied +selective +qualified +Devon +powder +clauses +expectation +tender +inclined +funded +alleged +hidden +ridge +exhibitions +lengths +Joyce +posed +explicitly +symbols +exploitation +receives +1950 +intermediate +Isabel +blocked +trophy +launch +spotted +manufacture +diesel +masses +protective +paint +budgets +Lisa +grows +fortunate +deserve +lap +concerns +varies +compliance +defensive +damage +objections +qualify +featured +suite +salmon +reach +requests +objection +devoted +thesis +repeatedly +blow +palm +Austria +Rover +parked +Carter +Guinness +temporarily +Land +south-east +chains +worthy +ozone +pursuit +valued +divine +react +deals +head +phoned +carrier +jeans +feedback +dancing +tales +rally +grant +performing +rush +handicap +consisting +counted +qualification +guaranteed +negligence +continuity +lend +offers +educated +stuck +surplus +swallowed +eagle +printing +land +Willie +novels +driving +dependence +1st +eighth +Craig +organise +Cornwall +orange +diameter +1939 +toward +auction +eating +Max +invisible +determining +construct +faculty +offenders +occurring +Pete +charm +Don +suffering +contempt +Wimbledon +reinforced +specify +misery +dropping +breasts +overall +Sara +jewellery +bacteria +sin +comparisons +privatisation +owe +squadron +grave +codes +circular +misleading +centred +sunlight +lowered +invested +mathematical +proteins +sanctions +aggression +caution +loch +reply +direct +subjected +inappropriate +diagram +terribly +St +human +liquid +solar +angles +sorted +persistent +poles +laying +inherited +phrases +doubtful +calcium +shake +ingredients +Sophie +admits +black +BR +monster +flames +allowances +sustain +needle +telecommunications +sphere +revenues +guessed +bowel +doubled +prints +rangers +accountants +screaming +legend +petition +predominantly +manual +lies +premium +photo +surroundings +spots +gravel +19th +architectural +bold +Maastricht +inheritance +Harvey +knock +blues +beyond +Day +emergence +beautifully +deeper +intact +cooperation +convince +incredible +sound +devoted +conduct +united +celebration +abruptly +considers +flights +explored +loves +blue +Derby +restriction +prior +submit +gaining +Santa +morality +tragic +musicians +invite +Ipswich +selling +script +coupled +tap +remark +consist +respectable +pint +optimistic +humanity +layout +openly +breed +policemen +Scots +invented +linking +convincing +Harold +guide +vocabulary +Rob +unacceptable +competent +Carrie +spatial +ignoring +applicant +swiftly +easier +painter +decisive +traders +pretend +bargaining +depended +modes +preventing +rage +respective +elite +permanently +seemingly +bunch +carers +fathers +engagement +liquid +Canterbury +binding +fires +sentenced +rebels +founder +ballet +erosion +Gould +syndrome +relieved +nursing +harmony +Coventry +protested +hut +sits +stops +Lamont +bore +instructed +fertility +toxic +testament +1957 +sickness +stretch +Bath +lemon +practise +mix +faster +integral +select +redundant +handle +throne +conceived +polytechnic +nerves +belongs +privately +burn +gravity +labelled +Alfred +bishops +basin +rings +holders +swing +flood +Christie +evolved +sovereignty +then +applicants +cows +lion +Virginia +trail +smoking +trading +Murray +boxing +amateur +probable +scrutiny +tempted +borders +pan +fix +hydrogen +accountability +consulted +echo +sponsorship +fame +El +lakes +protests +patience +documentation +Geoff +backing +search +Mozart +silently +passing +seasons +recipe +fetch +auditors +territorial +specified +abandon +bombs +Los +mineral +horizontal +lined +Robyn +booked +du +cleaning +bear +old-fashioned +inland +youngest +envisaged +floors +thrust +likewise +strengthen +penny +wake +Bradford +overseas +consult +cognitive +Ralph +dock +reaches +disturbed +communists +slim +synthesis +contexts +revival +Reading +regulatory +hurried +defender +dry +miserable +walks +debates +dancing +isolated +venue +Hampshire +resident +rounds +deals +packet +likelihood +remaining +induced +guys +temper +comparatively +calculations +protecting +holdings +corn +1947 +Yeltsin +fusion +Marxist +conferences +creditors +questionnaire +gothic +scared +willingness +civilian +shelves +reporting +precision +divide +Phillips +overnight +Intel +Linda +deputies +Indians +Trevor +Juliet +Watson +conventions +modified +instant +praise +Des +coin +blown +hiding +galleries +1940 +Constance +outlook +incurred +adverse +subsidiary +tiles +seventeenth +Korean +emphasised +Eddie +bile +1959 +fancy +accounting +leaflet +headmaster +crack +heels +truck +engage +reporter +plays +Steven +calm +initiated +brigade +Dorothy +unconscious +convicted +illustration +trustees +sustained +alike +End +ideally +entity +tons +sang +telegraph +negotiation +opposite +smell +aesthetic +wiped +concentrating +anonymous +trace +usage +orthodox +fulfil +polite +girlfriend +lovers +translated +static +intent +cancelled +inside +unaware +presidency +corps +assigned +appearances +exploit +margins +worldwide +cups +solved +panels +halt +EEC +Suffolk +developers +fantastic +Lancaster +seminar +fashionable +criticisms +Cooper +motorway +zones +foolish +intake +advances +receipt +rule +regiment +trades +manual +backs +duck +causal +convey +Tommy +wee +cleaning +fond +compatible +Southampton +inclusion +Herbert +finding +lengthy +two-thirds +tent +shed +implicit +cameras +dare +abolition +Romania +pigs +lace +dedicated +cuts +perceptions +ft +counts +earning +kiss +confirmation +dual +confronted +twenty-five +mistress +assignment +propaganda +toys +Arsenal +Eleanor +critic +curiosity +republican +pipes +reduces +shooting +cheerful +reporting +plea +distinguished +subjective +pie +priests +returns +tel +labels +width +relaxation +advertisement +white +smoke +pencil +legally +following +lacked +surviving +disadvantage +ruling +forward +sleeping +owl +adequately +reproduction +rewards +architects +rear +Shelley +exotic +ambassador +1914 +camps +displays +passages +gazed +timetable +salad +purple +cautious +visiting +Turner +aggregate +ignorance +anticipated +Parker +redundancy +array +penalties +renaissance +theology +try +warn +process +ethical +major +proving +plain +protestant +grid +tenth +takeover +canvas +Ted +skull +highlighted +jokes +beat +pools +twins +borne +criticized +chemical +omitted +revision +sincerely +prizes +salvation +teenage +responding +indicators +repairs +amnesty +comparing +large-scale +yield +Claire +photography +disastrous +thumb +dying +jointly +kilometres +scholars +ace +lump +delicious +confidential +clash +market +underground +Blanche +armed +destination +witnessed +parameters +costly +restraint +bit +1958 +shaped +rode +tips +prosperity +diamond +fury +instinct +reserved +valuation +contacted +subsidies +Hunt +collector +Darwin +sponsored +compound +strengths +sank +defences +lifestyle +prejudice +announce +apparatus +dot +shoe +blanket +wound +Christine +hunger +cabin +photographer +stay +preservation +calendar +assessments +colony +Katherine +thorough +medal +trips +washing +eliminate +breathe +actress +provinces +helicopter +mist +clue +dominance +relaxed +analysts +searched +grin +Czechoslovakia +hitting +inability +portion +restrict +Gray +conspiracy +Nicholson +mercy +log +autonomous +intends +solidarity +jail +genius +1920s +pilots +incorporate +atomic +blade +frozen +1956 +colourful +discharge +injured +mask +provided that +Trent +ease +draws +retire +supposed +ml +angrily +sigh +stamp +adjust +ferry +concessions +majesty +Gilbert +pylori +uniform +adjusted +ashamed +admired +alpha +referee +1944 +Lebanon +respondents +Collins +rested +reconstruction +flown +individually +jaw +submission +efficiently +bitterly +glorious +pour +illustrates +Angeles +amid +convert +wicked +provoked +Chapman +elbow +videos +coherent +annually +le +rising +disciplines +cliff +boyfriend +novel +controls +sweat +depths +Claudia +cave +balanced +strikes +stretching +pains +Close +Tokyo +Portugal +racism +priced +delightful +evaluate +arbitrary +Chicago +Richards +signature +reversed +heroes +clarity +hit +screamed +adjacent +lid +psychiatric +comprising +honey +temptation +beam +immigration +recordings +worrying +weird +practitioner +unchanged +calculation +tutor +politician +rolling +Athens +expedition +electorate +evolutionary +scattered +abolished +researcher +ports +Chester +dilemma +Carl +loaded +IMF +flung +intimate +fever +parallel +tight +miracle +including +lawn +biology +Lothian +failures +breaks +Angela +shy +appraisal +sporting +wines +cleaned +disciplinary +occurrence +smile +formidable +lexical +graduates +fined +cooking +privacy +needles +Reagan +Black +Ed +sink +march +equations +grim +narrative +HP +charts +polls +Paula +express +OK +limbs +decorated +high +addressing +proceeds +pact +Madame +Merseyside +revenge +vice-president +far from +proceeded +airline +minerals +killing +accused +double +gradual +descent +mount +homeless +courtesy +enhanced +supermarket +Blake +Cheshire +interfere +organisers +managing +monitoring +coming +rat +supporting +Marcus +trace +approve +delays +pm +Reynolds +please +yo +programming +training +renewed +Hull +invention +writings +back +excess +planes +legacy +challenges +gaps +dug +Jason +interpretations +smallest +pulse +analyses +Ashley +rubber +retired +specimen +outdoor +shooting +chosen +embarrassing +wrist +atoms +Hereford +smoking +incidentally +preferred +renewal +Japanese +vanished +hook +loudly +bride +Annie +interactions +bizarre +gospel +realm +mainland +knit +appalling +exchanges +surgeon +crews +orientation +twisted +occupy +flame +hatred +exceed +Maurice +laboratories +reviews +Bosnia +agreed +Butler +utility +conversations +imaginative +pursuing +flour +accepted +wartime +governing +Reid +object +cutting +indirectly +governed +palestinian +vocational +von +modification +slopes +allegedly +parade +free +aluminium +Al +movies +biscuits +motive +register +merchants +hip +print +rabbits +remedies +stress +trainer +welcome +wound +Geneva +configuration +boost +puzzled +encounter +axis +no matter how +Clive +worldwide +Arnold +Allan +lamb +laser +vegetation +reluctance +jazz +databases +strengthened +protocol +enjoyment +organisational +knitting +census +calculate +handicapped +mucosa +theirs +1951 +advertisements +eldest +Carol +1936 +eventual +husbands +fur +followers +wasted +pump +lifting +practised +yacht +toes +stimulate +speeches +1953 +traced +ensured +arrow +journals +weekends +spontaneous +appoint +1949 +binding +superintendent +vivid +corporations +organisms +celebrated +mice +motives +torn +tie +dies +1954 +waiting +classified +organs +lack +worlds +nineteenth-century +faithful +shield +withdrew +reckoned +north-west +rolling +missiles +noisy +hire +organising +quote +sofa +reminder +Venice +ministerial +A +daylight +injection +graph +exchanged +prayers +boost +preparations +borrowing +innovative +strongest +audiences +disclosure +confrontation +constitutes +burnt +liaison +armies +strangely +wounds +Hewlett-Packard +controlled +Newman +cease +incentives +extends +Mitchell +echoed +facilitate +resentment +shout +cage +gloves +1990s +exploring +saving +Leonard +crossing +choir +Gibson +exit +Sydney +assumes +woodland +fog +underneath +promises +Ellen +nationalism +Kenya +commentators +Ferguson +metals +reasoning +acids +hunt +pop +1946 +dirt +Texas +Keynes +conceptual +aiming +stating +technically +heading +economically +constituted +Union +maker +blowing +touching +tours +erected +ambitions +spare +chorus +Bond +bladder +settings +dividends +18th +Gaulle +unusually +phases +adapt +colitis +exploded +Nelson +civic +bells +gall +Macdonald +unwilling +retreat +booklet +enforce +defining +goodbye +meaningful +Gregory +pine +borrowed +bow +disturbing \ No newline at end of file diff --git a/setup/src/Magento/Setup/Model/Complex/Pattern.php b/setup/src/Magento/Setup/Model/Complex/Pattern.php index ee1356ff4d174ea03ad144d2db8b2b6a2ed951d7..a6569b3657de9373b4d459650087a9b177ef5658 100644 --- a/setup/src/Magento/Setup/Model/Complex/Pattern.php +++ b/setup/src/Magento/Setup/Model/Complex/Pattern.php @@ -104,7 +104,7 @@ class Pattern foreach ($this->getHeaders() as $key) { if (isset($row[$key])) { if (is_callable($row[$key])) { - $row[$key] = call_user_func($row[$key], $index); + $row[$key] = call_user_func($row[$key], $index, $generatorKey); } else { $row[$key] = str_replace('%s', $index, $row[$key]); } diff --git a/setup/src/Magento/Setup/Model/DataGenerator.php b/setup/src/Magento/Setup/Model/DataGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..7ea52bdb26aea5067d98726c52aab00ad8366d6b --- /dev/null +++ b/setup/src/Magento/Setup/Model/DataGenerator.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * A custom adapter that allows generating arbitrary descriptions + */ +namespace Magento\Setup\Model; + +class DataGenerator +{ + /** + * Location for dictionary file. + * + * @var string + */ + private $dictionaryFile; + + /** + * Dictionary data. + * + * @var array + */ + private $dictionaryData; + + /** + * Map of generated values + * + * @var array + */ + private $generatedValues; + + /** + * DataGenerator constructor. + * + * @param string $dictionaryFile + */ + public function __construct($dictionaryFile) + { + $this->dictionaryFile = $dictionaryFile; + $this->readData(); + $this->generatedValues = []; + } + + /** + * Read data from file. + * + * @return void + */ + protected function readData() + { + $f = fopen($this->dictionaryFile, 'r'); + while (!feof($f) && is_array($line = fgetcsv($f))) { + $this->dictionaryData[] = $line[0]; + } + } + + /** + * Generate string of random word data. + * + * @param int $minAmountOfWords + * @param int $maxAmountOfWords + * @param string|null $key + * @return string + */ + public function generate($minAmountOfWords, $maxAmountOfWords, $key = null) + { + $numberOfWords = mt_rand($minAmountOfWords, $maxAmountOfWords); + $result = ''; + + if ($key === null || !array_key_exists($key, $this->generatedValues)) { + for ($i = 0; $i < $numberOfWords; $i++) { + $result .= ' ' . $this->dictionaryData[mt_rand(0, count($this->dictionaryData) - 1)]; + } + $result = trim($result); + + if ($key !== null) { + $this->generatedValues[$key] = $result; + } + } else { + $result = $this->generatedValues[$key]; + } + return $result; + } +} diff --git a/setup/src/Magento/Setup/Model/PackagesData.php b/setup/src/Magento/Setup/Model/PackagesData.php index 701393fd9666102147c5921ad1831a69488ba8fb..1fc8e9db2bf64f3874bbfa457eb3384d91425863 100644 --- a/setup/src/Magento/Setup/Model/PackagesData.php +++ b/setup/src/Magento/Setup/Model/PackagesData.php @@ -409,6 +409,7 @@ class PackagesData return in_array( $item['package_type'], [ + \Magento\Setup\Model\Grid\TypeMapper::LANGUAGE_PACKAGE_TYPE, \Magento\Setup\Model\Grid\TypeMapper::MODULE_PACKAGE_TYPE, \Magento\Setup\Model\Grid\TypeMapper::EXTENSION_PACKAGE_TYPE, \Magento\Setup\Model\Grid\TypeMapper::THEME_PACKAGE_TYPE, @@ -491,26 +492,38 @@ class PackagesData return array_keys($packageVersions); } - } else { - $versionsPattern = '/^versions\s*\:\s(.+)$/m'; - - $commandParams = [ - self::PARAM_COMMAND => self::COMPOSER_SHOW, - self::PARAM_PACKAGE => $package, - self::PARAM_AVAILABLE => true - ]; - - $applicationFactory = $this->objectManagerProvider->get() - ->get(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class); - /** @var \Magento\Composer\MagentoComposerApplication $application */ - $application = $applicationFactory->create(); - - $result = $application->runComposerCommand($commandParams); - $matches = []; - preg_match($versionsPattern, $result, $matches); - if (isset($matches[1])) { - return explode(', ', $matches[1]); - } + } + + return $this->getAvailableVersionsFromAllRepositories($package); + } + + /** + * Get available versions of package by "composer show" command + * + * @param string $package + * @return array + * @exception \RuntimeException + */ + private function getAvailableVersionsFromAllRepositories($package) + { + $versionsPattern = '/^versions\s*\:\s(.+)$/m'; + + $commandParams = [ + self::PARAM_COMMAND => self::COMPOSER_SHOW, + self::PARAM_PACKAGE => $package, + self::PARAM_AVAILABLE => true + ]; + + $applicationFactory = $this->objectManagerProvider->get() + ->get(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class); + /** @var \Magento\Composer\MagentoComposerApplication $application */ + $application = $applicationFactory->create(); + + $result = $application->runComposerCommand($commandParams); + $matches = []; + preg_match($versionsPattern, $result, $matches); + if (isset($matches[1])) { + return explode(', ', $matches[1]); } throw new \RuntimeException( diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSetsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSetsFixtureTest.php new file mode 100644 index 0000000000000000000000000000000000000000..674f444fbde10666b75aa5dadfef3719e3177152 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/AttributeSetsFixtureTest.php @@ -0,0 +1,288 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Test\Unit\Fixtures; + +use \Magento\Setup\Fixtures\AttributeSetsFixture; + +/** + * @SuppressWarnings(PHPMD) + */ +class AttributeSetsFixtureTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Setup\Fixtures\FixtureModel + */ + private $fixtureModelMock; + + /** + * @var \Magento\Setup\Fixtures\AttributeSetsFixture + */ + private $model; + + public function setUp() + { + $this->fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new AttributeSetsFixture($this->fixtureModelMock); + } + + public function testExecute() + { + $attributeSets = [ + 'attribute_set' => [ + [ + 'name' => 'attribute set name', + 'attributes' => [ + 'attribute' => [ + [ + 'is_required' => 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'default_value' => 'yellow1', + 'attribute_code' => 'mycolor', + 'is_searchable' => '1', + 'frontend_label' => 'mycolor', + 'frontend_input' => 'select', + 'options' => [ + 'option' => [ + [ + 'label' => 'yellow1', + 'value' => '' + ] + ] + ] + ] + ] + ] + ] + ] + ]; + $attributeSet = $attributeSets['attribute_set'][0]; + + // Mock Attribute Sets + $attributeSetMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeSetInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeSetMock->expects($this->once()) + ->method('setAttributeSetName') + ->with("attribute set name"); + $attributeSetMock->expects($this->once()) + ->method('setEntityTypeId') + ->with(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE); + $attributeSetMock->expects($this->any()) + ->method('getAttributeSetName') + ->willReturn($attributeSet['name']); + + $attributeSetFactoryMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeSetInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $attributeSetFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($attributeSetMock); + + $attributeSetManagementMock = $this->getMockBuilder(\Magento\Catalog\Api\AttributeSetManagementInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeSetManagementMock->expects($this->once()) + ->method('create') + ->with($attributeSetMock, '4') + ->willReturn($attributeSetMock); + + //Mock Attribute Groups + $attributeGroupMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeGroupInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeGroupMock->expects($this->once()) + ->method('setAttributeGroupName') + ->with($attributeSetMock->getAttributeSetName() . ' - Group'); + $attributeGroupMock->expects($this->once()) + ->method('setAttributeSetId') + ->with($attributeSetMock->getAttributeSetId()); + + $attributeGroupFactoryMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeGroupInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $attributeGroupFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($attributeGroupMock); + + $productAttributeGroupRepoMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeGroupRepoMock->expects($this->once()) + ->method('save') + ->with($attributeGroupMock) + ->willReturn($attributeGroupMock); + + // Mock Attributes + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $attributeFactoryMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $attributeFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($attributeMock); + + //Mock Attribute Options + $optionMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeOptionInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $optionFactoryMock = $this->getMockBuilder(\Magento\Eav\Api\Data\AttributeOptionInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $optionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($optionMock); + + $productAttributeRepoMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeRepoMock->expects($this->once()) + ->method('save') + ->with($attributeMock) + ->willReturn($attributeMock); + + $productAttributeManagementMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeManagementInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeManagementMock->expects($this->once()) + ->method('assign') + ->willReturn($attributeMock->getAttributeId()); + + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManagerMock->expects($this->at(0)) + ->method('create') + ->willReturn($attributeSetManagementMock); + $objectManagerMock->expects($this->at(1)) + ->method('create') + ->willReturn($productAttributeGroupRepoMock); + $objectManagerMock->expects($this->at(2)) + ->method('create') + ->willReturn($attributeSetFactoryMock); + $objectManagerMock->expects($this->at(3)) + ->method('create') + ->willReturn($attributeGroupFactoryMock); + $objectManagerMock->expects($this->at(4)) + ->method('create') + ->willReturn($productAttributeRepoMock); + $objectManagerMock->expects($this->at(5)) + ->method('create') + ->willReturn($productAttributeManagementMock); + $objectManagerMock->expects($this->at(6)) + ->method('create') + ->willReturn($attributeFactoryMock); + $objectManagerMock->expects($this->at(7)) + ->method('create') + ->willReturn($optionFactoryMock); + + $this->fixtureModelMock + ->expects($this->once()) + ->method('getValue') + ->willReturn($attributeSets); + + $this->fixtureModelMock + ->expects($this->any()) + ->method('getObjectManager') + ->will($this->returnValue($objectManagerMock)); + + $this->model->execute(); + } + + public function testNoFixtureConfigValue() + { + $attributeSetManagementMock = $this->getMockBuilder(\Magento\Catalog\Api\AttributeSetManagementInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeSetManagementMock->expects($this->never())->method('create'); + + $productAttributeGroupRepoMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeGroupRepoMock->expects($this->never())->method('save'); + + $productAttributeRepoMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeRepoMock->expects($this->never())->method('save'); + + $productAttributeManagementMock = $this->getMockBuilder( + \Magento\Catalog\Api\ProductAttributeManagementInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $productAttributeManagementMock->expects($this->never())->method('assign'); + + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerMock->expects($this->never()) + ->method('create') + ->with($this->equalTo(\Magento\Catalog\Api\AttributeSetManagementInterface::class)) + ->willReturn($attributeSetManagementMock); + $objectManagerMock->expects($this->never()) + ->method('create') + ->with($this->equalTo(\Magento\Catalog\Api\ProductAttributeGroupRepositoryInterface::class)) + ->willReturn($productAttributeGroupRepoMock); + $objectManagerMock->expects($this->never()) + ->method('create') + ->with($this->equalTo(\Magento\Catalog\Api\ProductAttributeRepositoryInterface::class)) + ->willReturn($productAttributeRepoMock); + $objectManagerMock->expects($this->never()) + ->method('create') + ->with($this->equalTo(\Magento\Catalog\Api\ProductAttributeManagementInterface::class)) + ->willReturn($productAttributeManagementMock); + + $this->fixtureModelMock + ->expects($this->never()) + ->method('getObjectManager') + ->will($this->returnValue($objectManagerMock)); + $this->fixtureModelMock + ->expects($this->once()) + ->method('getValue') + ->willReturn(null); + + $this->model->execute(); + } + + public function testGetActionTitle() + { + $this->assertSame('Generating attribute sets', $this->model->getActionTitle()); + } + + public function testIntroduceParamLabels() + { + $this->assertSame([ + 'attribute_sets' => 'Attribute Sets' + ], $this->model->introduceParamLabels()); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigurableProductsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigurableProductsFixtureTest.php index 7e89f7d807ed8704156fcffd5252185c0a2e0e54..7f2676c53e232ef152b9ad23a990b40f891e7115 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigurableProductsFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/ConfigurableProductsFixtureTest.php @@ -23,16 +23,25 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); + $this->fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); $this->model = new ConfigurableProductsFixture($this->fixtureModelMock); } + /** + * @SuppressWarnings(PHPMD) + */ public function testExecute() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); + $importMock = $this->getMockBuilder(\Magento\ImportExport\Model\Import::class) + ->disableOriginalConstructor() + ->getMock(); - $contextMock = $this->getMock(\Magento\Framework\Model\ResourceModel\Db\Context::class, [], [], '', false); + $contextMock = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\Context::class) + ->disableOriginalConstructor() + ->getMock(); $abstractDbMock = $this->getMockForAbstractClass( \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, [$contextMock], @@ -46,7 +55,9 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('getAllChildren') ->will($this->returnValue([1])); - $categoryMock = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); $categoryMock->expects($this->once()) ->method('getResource') ->will($this->returnValue($abstractDbMock)); @@ -60,12 +71,16 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('load') ->willReturnSelf(); - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); + $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->getMock(); $storeMock->expects($this->once()) ->method('getRootCategoryId') ->will($this->returnValue([2])); - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); + $websiteMock = $this->getMockBuilder(\Magento\Store\Model\Website::class) + ->disableOriginalConstructor() + ->getMock(); $websiteMock->expects($this->once()) ->method('getCode') ->will($this->returnValue('website_code')); @@ -73,15 +88,18 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('getGroups') ->will($this->returnValue([$storeMock])); - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); + $storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class) + ->disableOriginalConstructor() + ->getMock(); $storeManagerMock->expects($this->once()) ->method('getWebsites') ->will($this->returnValue([$websiteMock])); $source = $this->getMockBuilder(Generator::class)->disableOriginalConstructor()->getMock(); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); $objectManagerMock->expects($this->at(0)) ->method('get') ->with(\Magento\Store\Model\StoreManager::class) @@ -103,14 +121,61 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase $importMock->expects($this->once())->method('validateSource')->with($source)->willReturn(1); $importMock->expects($this->once())->method('importSource')->willReturn(1); + $valuesMap = [ + ['configurable_products', 0, 1], + ['simple_products', 0, 1], + ['search_terms', null, ['search_term' =>[['term' => 'iphone 6', 'count' => '1']]]], + ['configurable_products_variation', 3, 1], + [ + 'search_config', + null, + [ + 'max_amount_of_words_description' => '200', + 'max_amount_of_words_short_description' => '20', + 'min_amount_of_words_description' => '20', + 'min_amount_of_words_short_description' => '5' + ] + ], + ['attribute_sets', + null, + [ + 'attribute_set' => [ + [ + 'name' => 'attribute set name', + 'attributes' => [ + 'attribute' => [ + [ + 'is_required' => 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'default_value' => 'yellow1', + 'attribute_code' => 'mycolor', + 'is_searchable' => '1', + 'frontend_label' => 'mycolor', + 'frontend_input' => 'select', + 'options' => [ + 'option' => [ + [ + 'label' => 'yellow1', + 'value' => '' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + $this->fixtureModelMock ->expects($this->any()) ->method('getValue') - ->willReturnMap([ - ['configurable_products', 0, 1], - ['configurable_products_variation', 3, 1], - ]); - + ->will($this->returnValueMap($valuesMap)); $this->fixtureModelMock ->expects($this->atLeastOnce()) ->method('getObjectManager') @@ -121,11 +186,15 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase public function testNoFixtureConfigValue() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); + $importMock = $this->getMockBuilder(\Magento\ImportExport\Model\Import::class) + ->disableOriginalConstructor() + ->getMock(); $importMock->expects($this->never())->method('validateSource'); $importMock->expects($this->never())->method('importSource'); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); $objectManagerMock->expects($this->never()) ->method('create') ->with($this->equalTo(\Magento\ImportExport\Model\Import::class)) @@ -151,7 +220,7 @@ class ConfigurableProductsFixtureTest extends \PHPUnit_Framework_TestCase public function testIntroduceParamLabels() { $this->assertSame([ - 'configurable_products' => 'Configurable products', + 'configurable_products' => 'Configurable products' ], $this->model->introduceParamLabels()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php index ee1369bd15d5a46f084a504fdfedbf61eb53f8bc..32b00312880dee07e0f172f97df4668596088abd 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/FixtureModelTest.php @@ -55,11 +55,19 @@ class FixtureModelTest extends \PHPUnit_Framework_TestCase false ); - $fileParserMock = $this->getMock(\Magento\Framework\Xml\Parser::class, ['load', 'xmlToArray'], [], '', false); - $fileParserMock->expects($this->once())->method('xmlToArray')->willReturn( + $fileParserMock = $this->getMock(\Magento\Framework\Xml\Parser::class, ['getDom', 'xmlToArray'], [], '', false); + $fileParserMock->expects($this->exactly(2))->method('xmlToArray')->willReturn( ['config' => [ 'profile' => ['some_key' => 'some_value']]] ); - $fileParserMock->expects($this->once())->method('load')->with('config.file')->willReturn($fileParserMock); + + $domMock = $this->getMock(\DOMDocument::class, ['load', 'xinclude'], [], '', false); + $domMock->expects($this->once())->method('load')->with('config.file')->willReturn( + $fileParserMock->xmlToArray() + ); + $domMock->expects($this->once())->method('xinclude'); + + $fileParserMock->expects($this->exactly(2))->method('getDom')->willReturn($domMock); + $this->model = new FixtureModel($reindexCommandMock, $fileParserMock); $this->model->loadConfig('config.file'); $this->assertSame('some_value', $this->model->getValue('some_key')); diff --git a/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php b/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php index 913b2a1e3f02048ac4c61b894e287791ce421207..5e24863483992ce4ada367846fc9c3350a5082b7 100644 --- a/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Fixtures/SimpleProductsFixtureTest.php @@ -22,19 +22,28 @@ class SimpleProductsFixtureTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->fixtureModelMock = $this->getMock(\Magento\Setup\Fixtures\FixtureModel::class, [], [], '', false); + $this->fixtureModelMock = $this->getMockBuilder(\Magento\Setup\Fixtures\FixtureModel::class) + ->disableOriginalConstructor() + ->getMock(); $this->model = new SimpleProductsFixture($this->fixtureModelMock); } + /** + * @SuppressWarnings(PHPMD) + */ public function testExecute() { - $storeMock = $this->getMock(\Magento\Store\Model\Store::class, [], [], '', false); + $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->getMock(); $storeMock->expects($this->once()) ->method('getRootCategoryId') ->willReturn(1); - $websiteMock = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); + $websiteMock = $this->getMockBuilder(\Magento\Store\Model\Website::class) + ->disableOriginalConstructor() + ->getMock(); $websiteMock->expects($this->once()) ->method('getCode') ->willReturn('website_code'); @@ -42,14 +51,24 @@ class SimpleProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('getGroups') ->willReturn([$storeMock]); - $storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManager::class, [], [], '', false); + $storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class) + ->disableOriginalConstructor() + ->getMock(); $storeManagerMock->expects($this->once()) ->method('getWebsites') ->willReturn([$websiteMock]); - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); + $source = $this->getMockBuilder(\Magento\Setup\Model\Generator::class)->disableOriginalConstructor()->getMock(); + + $importMock = $this->getMockBuilder(\Magento\ImportExport\Model\Import::class) + ->disableOriginalConstructor() + ->getMock(); + $importMock->expects($this->once())->method('validateSource')->with($source)->willReturn(1); + $importMock->expects($this->once())->method('importSource')->willReturn(1); - $contextMock = $this->getMock(\Magento\Framework\Model\ResourceModel\Db\Context::class, [], [], '', false); + $contextMock = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\Context::class) + ->disableOriginalConstructor() + ->getMock(); $abstractDbMock = $this->getMockForAbstractClass( \Magento\Framework\Model\ResourceModel\Db\AbstractDb::class, [$contextMock], @@ -63,7 +82,9 @@ class SimpleProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('getAllChildren') ->will($this->returnValue([1])); - $categoryMock = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false); + $categoryMock = $this->getMockBuilder(\Magento\Catalog\Model\Category::class) + ->disableOriginalConstructor() + ->getMock(); $categoryMock->expects($this->once()) ->method('getResource') ->willReturn($abstractDbMock); @@ -77,35 +98,81 @@ class SimpleProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('getName') ->willReturn('category_name'); - $valueMap = [ - [ - \Magento\ImportExport\Model\Import::class, - [ - 'data' => [ - 'entity' => 'catalog_product', - 'behavior' => 'append', - 'validation_strategy' => 'validation-stop-on-errors' - ] - ], - $importMock - ], - [\Magento\Store\Model\StoreManager::class, [], $storeManagerMock] - ]; - - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); - $objectManagerMock->expects($this->exactly(2)) + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerMock->expects($this->at(0)) ->method('create') - ->will($this->returnValueMap($valueMap)); - $objectManagerMock->expects($this->once()) + ->with(\Magento\Store\Model\StoreManager::class) + ->willReturn($storeManagerMock); + $objectManagerMock->expects($this->at(1)) ->method('get') ->willReturn($categoryMock); + $objectManagerMock->expects($this->at(2)) + ->method('create') + ->with(\Magento\Setup\Model\Generator::class) + ->willReturn($source); + $objectManagerMock->expects($this->at(3)) + ->method('create') + ->with(\Magento\ImportExport\Model\Import::class) + ->willReturn($importMock); + $valuesMap = [ + ['simple_products', 0, 1], + ['configurable_products', 0, 1], + ['search_terms', null, ['search_term' =>[['term' => 'iphone 6', 'count' => '1']]]], + [ + 'search_config', + null, + [ + 'max_amount_of_words_description' => '200', + 'max_amount_of_words_short_description' => '20', + 'min_amount_of_words_description' => '20', + 'min_amount_of_words_short_description' => '5' + ] + ], + [ + 'attribute_sets', + null, + [ + 'attribute_set' => [ + [ + 'name' => 'attribute set name', + 'attributes' => [ + 'attribute' => [ + [ + 'is_required' => 1, + 'is_visible_on_front' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'default_value' => 'yellow1', + 'attribute_code' => 'mycolor', + 'is_searchable' => '1', + 'frontend_label' => 'mycolor', + 'frontend_input' => 'select', + 'options' => [ + 'option' => [ + [ + 'label' => 'yellow1', + 'value' => '' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; $this->fixtureModelMock - ->expects($this->once()) + ->expects($this->any()) ->method('getValue') - ->willReturn(1); + ->will($this->returnValueMap($valuesMap)); $this->fixtureModelMock - ->expects($this->exactly(3)) + ->expects($this->any()) ->method('getObjectManager') ->willReturn($objectManagerMock); @@ -114,11 +181,15 @@ class SimpleProductsFixtureTest extends \PHPUnit_Framework_TestCase public function testNoFixtureConfigValue() { - $importMock = $this->getMock(\Magento\ImportExport\Model\Import::class, [], [], '', false); + $importMock = $this->getMockBuilder(\Magento\ImportExport\Model\Import::class) + ->disableOriginalConstructor() + ->getMock(); $importMock->expects($this->never())->method('validateSource'); $importMock->expects($this->never())->method('importSource'); - $objectManagerMock = $this->getMock(\Magento\Framework\ObjectManager\ObjectManager::class, [], [], '', false); + $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->getMock(); $objectManagerMock->expects($this->never()) ->method('create') ->with($this->equalTo(\Magento\ImportExport\Model\Import::class)) @@ -129,7 +200,7 @@ class SimpleProductsFixtureTest extends \PHPUnit_Framework_TestCase ->method('getObjectManager') ->will($this->returnValue($objectManagerMock)); $this->fixtureModelMock - ->expects($this->once()) + ->expects($this->any()) ->method('getValue') ->willReturn(false); diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Complex/PatternTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Complex/PatternTest.php index 4adac2b0fb0edadf4a22d36f4fcd6f44d5357e35..0123fcd8ca032a79ff0b4ff5616747d8fb517bc7 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/Complex/PatternTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/Complex/PatternTest.php @@ -42,8 +42,8 @@ class PatternTest extends \PHPUnit_Framework_TestCase [ 'id' => '%s', 'name' => 'Static', - 'calculated' => function ($index) { - return $index * 10; + 'calculated' => function ($index, $generatedKey) { + return $index * 10 + $generatedKey; }, ], [ @@ -53,7 +53,7 @@ class PatternTest extends \PHPUnit_Framework_TestCase 'name' => 'yyy %s' ], ], - 'ecpectedCount' => 3, + 'expectedCount' => 3, 'expectedRowsResult' => [ ['id' => '1', 'name' => 'Static', 'calculated' => 10], ['id' => '', 'name' => 'xxx 1', 'calculated' => ''], @@ -68,7 +68,7 @@ class PatternTest extends \PHPUnit_Framework_TestCase 'calculated' => 'calc %s', ], ], - 'ecpectedCount' => 1, + 'expectedCount' => 1, 'expectedRowsResult' => [ ['id' => '1', 'name' => 'Dynamic 1', 'calculated' => 'calc 1'], ], diff --git a/setup/src/Magento/Setup/Test/Unit/Model/DataGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/DataGeneratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..65f92488c1a62fc62a4f36be3d8140ffe118eecd --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/DataGeneratorTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Test\Unit\Model; + +use Magento\Setup\Model\DataGenerator; + +class DataGeneratorTest extends \PHPUnit_Framework_TestCase +{ + + const PATH_TO_CSV_FILE = '/_files/dictionary.csv'; + + /** + * @test + * + * @return void + */ + public function testGenerate() + { + $data = file(__DIR__ . self::PATH_TO_CSV_FILE); + $wordCount = count($data); + $model = new DataGenerator(__DIR__ . self::PATH_TO_CSV_FILE); + $result = $model->generate($wordCount, $wordCount); + + $found = false; + foreach ($data as $word) { + $found = (strpos($result, $word[0]) !== false) || $found; + } + $this->assertTrue($found); + $this->assertEquals($wordCount, count(explode(" ", $result))); + } + + public function testGenerateWithKey() + { + $key = 'generate-test'; + + $data = file(__DIR__ . self::PATH_TO_CSV_FILE); + $wordCount = mt_rand(1, count($data)); + $model = new DataGenerator(__DIR__ . self::PATH_TO_CSV_FILE); + $result = $model->generate($wordCount, $wordCount, $key); + + $foundResult = $model->generate($wordCount, $wordCount, $key); + + $this->assertEquals($wordCount, count(explode(" ", $result))); + $this->assertEquals($result, $foundResult); + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Model/PackagesDataTest.php b/setup/src/Magento/Setup/Test/Unit/Model/PackagesDataTest.php index 5a7ead384665446fa7fb3dc82dff33d4b9777133..54a297e7dbe43909c0fe9e929aec95a0239beb73 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/PackagesDataTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/PackagesDataTest.php @@ -22,38 +22,96 @@ class PackagesDataTest extends \PHPUnit_Framework_TestCase */ private $packagesData; + /** + * @var ComposerInformation|MockObject + */ + private $composerInformation; + + /** + * @var \Magento\Setup\Model\DateTime\TimeZoneProvider|MockObject + */ + private $timeZoneProvider; + + /** + * @var \Magento\Setup\Model\PackagesAuth|MockObject + */ + private $packagesAuth; + + /** + * @var \Magento\Framework\Filesystem|MockObject + */ + private $filesystem; + + /** + * @var \Magento\Setup\Model\ObjectManagerProvider|MockObject + */ + private $objectManagerProvider; + + /** + * @var \Magento\Setup\Model\Grid\TypeMapper|MockObject + */ + private $typeMapper; + public function setUp() { - $composerInformation = $this->getComposerInformation(); - $timeZoneProvider = $this->getMock(\Magento\Setup\Model\DateTime\TimeZoneProvider::class, [], [], '', false); + $this->composerInformation = $this->getComposerInformation(); + $this->timeZoneProvider = $this->getMockBuilder(\Magento\Setup\Model\DateTime\TimeZoneProvider::class) + ->disableOriginalConstructor() + ->getMock(); $timeZone = $this->getMock(\Magento\Framework\Stdlib\DateTime\Timezone::class, [], [], '', false); - $timeZoneProvider->expects($this->any())->method('get')->willReturn($timeZone); - $packagesAuth = $this->getMock(\Magento\Setup\Model\PackagesAuth::class, [], [], '', false); - $filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); - $objectManagerProvider = $this->getMock(\Magento\Setup\Model\ObjectManagerProvider::class, [], [], '', false); + $this->timeZoneProvider->expects($this->any())->method('get')->willReturn($timeZone); + $this->packagesAuth = $this->getMock(\Magento\Setup\Model\PackagesAuth::class, [], [], '', false); + $this->filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); + $this->objectManagerProvider = $this->getMockBuilder(\Magento\Setup\Model\ObjectManagerProvider::class) + ->disableOriginalConstructor() + ->getMock(); $objectManager = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); - $applicationFactory = $this->getMock( - \Magento\Framework\Composer\MagentoComposerApplicationFactory::class, - [], - [], - '', - false - ); + $appFactory = $this->getMockBuilder(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class) + ->disableOriginalConstructor() + ->getMock(); $application = $this->getMock(\Magento\Composer\MagentoComposerApplication::class, [], [], '', false); $application->expects($this->any()) ->method('runComposerCommand') - ->willReturn('versions: 2.0.1'); - $applicationFactory->expects($this->any())->method('create')->willReturn($application); + ->willReturnMap([ + [ + [ + PackagesData::PARAM_COMMAND => PackagesData::COMPOSER_SHOW, + PackagesData::PARAM_PACKAGE => 'magento/package-1', + PackagesData::PARAM_AVAILABLE => true, + ], + null, + 'versions: 2.0.1' + ], + [ + [ + PackagesData::PARAM_COMMAND => PackagesData::COMPOSER_SHOW, + PackagesData::PARAM_PACKAGE => 'magento/package-2', + PackagesData::PARAM_AVAILABLE => true, + ], + null, + 'versions: 2.0.1' + ], + [ + [ + PackagesData::PARAM_COMMAND => PackagesData::COMPOSER_SHOW, + PackagesData::PARAM_PACKAGE => 'partner/package-3', + PackagesData::PARAM_AVAILABLE => true, + ], + null, + 'versions: 3.0.1' + ], + ]); + $appFactory->expects($this->any())->method('create')->willReturn($application); $objectManager->expects($this->any()) ->method('get') ->with(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class) - ->willReturn($applicationFactory); - $objectManagerProvider->expects($this->any())->method('get')->willReturn($objectManager); + ->willReturn($appFactory); + $this->objectManagerProvider->expects($this->any())->method('get')->willReturn($objectManager); $directoryWrite = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\WriteInterface::class); $directoryRead = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\ReadInterface::class); - $filesystem->expects($this->any())->method('getDirectoryRead')->will($this->returnValue($directoryRead)); - $filesystem->expects($this->any()) + $this->filesystem->expects($this->any())->method('getDirectoryRead')->will($this->returnValue($directoryRead)); + $this->filesystem->expects($this->any()) ->method('getDirectoryWrite') ->will($this->returnValue($directoryWrite)); $directoryWrite->expects($this->any())->method('isExist')->willReturn(true); @@ -81,32 +139,41 @@ class PackagesDataTest extends \PHPUnit_Framework_TestCase . '}}}' ); - $typeMapper = $this->getMockBuilder(\Magento\Setup\Model\Grid\TypeMapper::class) + $this->typeMapper = $this->getMockBuilder(\Magento\Setup\Model\Grid\TypeMapper::class) ->disableOriginalConstructor() ->getMock(); - $typeMapper->expects(static::any()) + $this->typeMapper->expects(static::any()) ->method('map') ->willReturnMap([ [ComposerInformation::MODULE_PACKAGE_TYPE, \Magento\Setup\Model\Grid\TypeMapper::MODULE_PACKAGE_TYPE], ]); + $this->createPackagesData(); + } + + private function createPackagesData() + { $this->packagesData = new PackagesData( - $composerInformation, - $timeZoneProvider, - $packagesAuth, - $filesystem, - $objectManagerProvider, - $typeMapper + $this->composerInformation, + $this->timeZoneProvider, + $this->packagesAuth, + $this->filesystem, + $this->objectManagerProvider, + $this->typeMapper ); } /** + * @param array $requiredPackages + * @param array $installedPackages + * @param array $repo * @return ComposerInformation|MockObject */ - private function getComposerInformation() + private function getComposerInformation($requiredPackages = [], $installedPackages = [], $repo = []) { $composerInformation = $this->getMock(ComposerInformation::class, [], [], '', false); $composerInformation->expects($this->any())->method('getInstalledMagentoPackages')->willReturn( + $installedPackages ?: [ 'magento/package-1' => [ 'name' => 'magento/package-1', @@ -117,21 +184,30 @@ class PackagesDataTest extends \PHPUnit_Framework_TestCase 'name' => 'magento/package-2', 'type' => 'magento2-module', 'version'=> '1.0.1' - ] + ], + 'partner/package-3' => [ + 'name' => 'partner/package-3', + 'type' => 'magento2-module', + 'version'=> '3.0.0' + ], ] ); $composerInformation->expects($this->any())->method('getRootRepositories') - ->willReturn(['repo1', 'repo2']); + ->willReturn($repo ?: ['repo1', 'repo2']); $composerInformation->expects($this->any())->method('getPackagesTypes') ->willReturn(['magento2-module']); $rootPackage = $this->getMock(RootPackage::class, [], ['magento/project', '2.1.0', '2']); $rootPackage->expects($this->any()) ->method('getRequires') - ->willReturn([ - 'magento/package-1' => '1.0.0', - 'magento/package-2' => '1.0.1' - ]); + ->willReturn( + $requiredPackages ?: + [ + 'magento/package-1' => '1.0.0', + 'magento/package-2' => '1.0.1', + 'partner/package-3' => '3.0.0', + ] + ); $composerInformation->expects($this->any()) ->method('getRootPackage') ->willReturn($rootPackage); @@ -146,19 +222,57 @@ class PackagesDataTest extends \PHPUnit_Framework_TestCase $this->assertArrayHasKey('date', $latestData['lastSyncDate']); $this->assertArrayHasKey('time', $latestData['lastSyncDate']); $this->assertArrayHasKey('packages', $latestData); - $this->assertSame(2, count($latestData['packages'])); - $this->assertSame(2, $latestData['countOfUpdate']); + $this->assertSame(3, count($latestData['packages'])); + $this->assertSame(3, $latestData['countOfUpdate']); $this->assertArrayHasKey('installPackages', $latestData); $this->assertSame(1, count($latestData['installPackages'])); $this->assertSame(1, $latestData['countOfInstall']); } - public function testGetPackagesForUpdate() + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Couldn't get available versions for package partner/package-4 + */ + public function testGetPackagesForUpdateWithException() { + $requiredPackages = [ + 'partner/package-4' => '4.0.4', + ]; + $installedPackages = [ + 'partner/package-4' => [ + 'name' => 'partner/package-4', + 'type' => 'magento2-module', + 'version'=> '4.0.4' + ], + ]; + $this->composerInformation = $this->getComposerInformation($requiredPackages, $installedPackages); + $this->createPackagesData(); + $this->packagesData->getPackagesForUpdate(); + } + + public function testPackagesForUpdateFromJson() + { + $this->composerInformation = $this->getComposerInformation([], [], ['https://repo1']); + $this->packagesAuth->expects($this->atLeastOnce()) + ->method('getCredentialBaseUrl') + ->willReturn('repo1'); + $this->createPackagesData(); $packages = $this->packagesData->getPackagesForUpdate(); $this->assertEquals(2, count($packages)); $this->assertArrayHasKey('magento/package-1', $packages); + $this->assertArrayHasKey('partner/package-3', $packages); + $firstPackage = array_values($packages)[0]; + $this->assertArrayHasKey('latestVersion', $firstPackage); + $this->assertArrayHasKey('versions', $firstPackage); + } + + public function testGetPackagesForUpdate() + { + $packages = $this->packagesData->getPackagesForUpdate(); + $this->assertEquals(3, count($packages)); + $this->assertArrayHasKey('magento/package-1', $packages); $this->assertArrayHasKey('magento/package-2', $packages); + $this->assertArrayHasKey('partner/package-3', $packages); $firstPackage = array_values($packages)[0]; $this->assertArrayHasKey('latestVersion', $firstPackage); $this->assertArrayHasKey('versions', $firstPackage); @@ -167,9 +281,10 @@ class PackagesDataTest extends \PHPUnit_Framework_TestCase public function testGetInstalledPackages() { $installedPackages = $this->packagesData->getInstalledPackages(); - $this->assertEquals(2, count($installedPackages)); + $this->assertEquals(3, count($installedPackages)); $this->assertArrayHasKey('magento/package-1', $installedPackages); $this->assertArrayHasKey('magento/package-2', $installedPackages); + $this->assertArrayHasKey('partner/package-3', $installedPackages); } public function testGetMetaPackagesMap() diff --git a/setup/src/Magento/Setup/Test/Unit/Model/_files/dictionary.csv b/setup/src/Magento/Setup/Test/Unit/Model/_files/dictionary.csv new file mode 100644 index 0000000000000000000000000000000000000000..da9dfd0f0ca08670a550cbfcd2af61526f9eede1 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/_files/dictionary.csv @@ -0,0 +1,5 @@ +one +two +three +four +five \ No newline at end of file