diff --git a/.gitignore b/.gitignore index 61d970a274b8dc1a573bf8fd31083ab984197bff..1dc24183ad30092742a351bd094b4ae5573052be 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ atlassian* /var/* !/var/.htaccess /vendor +!/vendor/.htaccess diff --git a/.htaccess b/.htaccess index 33b77523559c09c9e75f8704edd65f077043ae9d..a0d1710a8aa55721fbdae884b0018c1a9d254c95 100644 --- a/.htaccess +++ b/.htaccess @@ -171,13 +171,83 @@ </IfModule> ########################################### -## Deny access to release notes to prevent disclosure of the installed Magento version +## Deny access to root files to hide sensitive application information + RedirectMatch 404 /\.git - <Files RELEASE_NOTES.txt> - Order allow,deny - Deny from all + <Files composer.json> + order allow,deny + deny from all </Files> -############################################ + <Files composer.lock> + order allow,deny + deny from all + </Files> + <Files .gitignore> + order allow,deny + deny from all + </Files> + <Files .htaccess> + order allow,deny + deny from all + </Files> + <Files .htaccess.sample> + order allow,deny + deny from all + </Files> + <Files .php_cs> + order allow,deny + deny from all + </Files> + <Files .travis.yml> + order allow,deny + deny from all + </Files> + <Files CHANGELOG.md> + order allow,deny + deny from all + </Files> + <Files CONTRIBUTING.md> + order allow,deny + deny from all + </Files> + <Files CONTRIBUTOR_LICENSE_AGREEMENT.html> + order allow,deny + deny from all + </Files> + <Files COPYING.txt> + order allow,deny + deny from all + </Files> + <Files Gruntfile.js> + order allow,deny + deny from all + </Files> + <Files LICENSE.txt> + order allow,deny + deny from all + </Files> + <Files LICENSE_AFL.txt> + order allow,deny + deny from all + </Files> + <Files nginx.conf.sample> + order allow,deny + deny from all + </Files> + <Files package.json> + order allow,deny + deny from all + </Files> + <Files php.ini.sample> + order allow,deny + deny from all + </Files> + <Files README.md> + order allow,deny + deny from all + </Files> + +################################ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags diff --git a/.htaccess.sample b/.htaccess.sample index 133ce7de2c59badd5df4a159bb921e8a99f05eb7..891dad19d642b5f958c9c33c71476dd4d39247c8 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -36,7 +36,7 @@ ############################################ ## adjust memory limit - php_value memory_limit 256M + php_value memory_limit 768M php_value max_execution_time 18000 ############################################ @@ -65,13 +65,6 @@ SecFilterScanPOST Off </IfModule> -<IfModule mod_headers.c> -############################################ -## prevent clickjacking - - Header set X-Frame-Options SAMEORIGIN -</IfModule> - <IfModule mod_deflate.c> ############################################ @@ -136,9 +129,11 @@ RewriteRule .* - [L,R=405] ############################################ -## always send 404 on missing files in these folders +## redirect for mobile user agents - RewriteCond %{REQUEST_URI} !^/pub/(media|js)/ + #RewriteCond %{REQUEST_URI} !^/mobiledirectoryhere/.*$ + #RewriteCond %{HTTP_USER_AGENT} "android|blackberry|ipad|iphone|ipod|iemobile|opera mobile|palmos|webos|googlebot-mobile" [NC] + #RewriteRule ^(.*)$ /mobiledirectoryhere/ [L,R=302] ############################################ ## never rewrite for existing files, directories and links @@ -175,16 +170,84 @@ </IfModule> ########################################### -## Deny access to release notes to prevent disclosure of the installed Magento version +## Deny access to root files to hide sensitive application information + RedirectMatch 404 /\.git - <Files RELEASE_NOTES.txt> - Order allow,deny - Deny from all + <Files composer.json> + order allow,deny + deny from all + </Files> + <Files composer.lock> + order allow,deny + deny from all + </Files> + <Files .gitignore> + order allow,deny + deny from all + </Files> + <Files .htaccess> + order allow,deny + deny from all + </Files> + <Files .htaccess.sample> + order allow,deny + deny from all + </Files> + <Files .php_cs> + order allow,deny + deny from all + </Files> + <Files .travis.yml> + order allow,deny + deny from all + </Files> + <Files CHANGELOG.md> + order allow,deny + deny from all + </Files> + <Files CONTRIBUTING.md> + order allow,deny + deny from all + </Files> + <Files CONTRIBUTOR_LICENSE_AGREEMENT.html> + order allow,deny + deny from all + </Files> + <Files COPYING.txt> + order allow,deny + deny from all + </Files> + <Files Gruntfile.js> + order allow,deny + deny from all + </Files> + <Files LICENSE.txt> + order allow,deny + deny from all + </Files> + <Files LICENSE_AFL.txt> + order allow,deny + deny from all + </Files> + <Files nginx.conf.sample> + order allow,deny + deny from all + </Files> + <Files package.json> + order allow,deny + deny from all + </Files> + <Files php.ini.sample> + order allow,deny + deny from all + </Files> + <Files README.md> + order allow,deny + deny from all </Files> -############################################ +################################ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags #FileETag none - diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php index 3a2950d1de0639c2f6f6eac23ba0a0392b358334..8d3fc32ba7f27b824b5fbd8ccec542a181801a8e 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -47,11 +47,6 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product */ protected $_passTierPrice = 0; - /** - * @var int - */ - protected $_passGroupPrice = 0; - /** * List of items websites * @@ -59,7 +54,6 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product */ protected $_priceWebsite = [ ImportAdvancedPricing::COL_TIER_PRICE_WEBSITE, - ImportAdvancedPricing::COL_GROUP_PRICE_WEBSITE, ]; /** @@ -69,7 +63,6 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product */ protected $_priceCustomerGroup = [ ImportAdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP, - ImportAdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP, ]; /** @@ -79,9 +72,6 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product */ protected $templateExportData = [ ImportAdvancedPricing::COL_SKU => '', - ImportAdvancedPricing::COL_GROUP_PRICE_WEBSITE => '', - ImportAdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => '', - ImportAdvancedPricing::COL_GROUP_PRICE => '', ImportAdvancedPricing::COL_TIER_PRICE_WEBSITE => '', ImportAdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => '', ImportAdvancedPricing::COL_TIER_PRICE_QTY => '', @@ -246,14 +236,6 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product ) { $this->_passTierPrice = 1; } - if ($attribute->getAttributeCode() == ImportAdvancedPricing::COL_GROUP_PRICE - && in_array( - $attribute->getId(), - $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_SKIP] - ) - ) { - $this->_passGroupPrice = 1; - } } $collection->removeItemByKey($attribute->getId()); } @@ -277,16 +259,10 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product $rawData = $this->collectRawData(); $productIds = array_keys($rawData); if (isset($productIds)) { - if (!$this->_passGroupPrice) { - $exportData = array_merge( - $exportData, - $this->getTierAndGroupPrices($productIds, ImportAdvancedPricing::TABLE_GROUPED_PRICE) - ); - } if (!$this->_passTierPrice) { $exportData = array_merge( $exportData, - $this->getTierAndGroupPrices($productIds, ImportAdvancedPricing::TABLE_TIER_PRICE) + $this->getTierPrices($productIds, ImportAdvancedPricing::TABLE_TIER_PRICE) ); } } @@ -347,22 +323,12 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function getTierAndGroupPrices(array $listSku, $table) + protected function getTierPrices(array $listSku, $table) { if (isset($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP])) { $exportFilter = $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]; } - if ($table == ImportAdvancedPricing::TABLE_GROUPED_PRICE) { - $selectFields = [ - ImportAdvancedPricing::COL_SKU => 'cpe.sku', - ImportAdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'ap.website_id', - ImportAdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'ap.customer_group_id', - ImportAdvancedPricing::COL_GROUP_PRICE => 'ap.value' - ]; - if (isset($exportFilter) && !empty($exportFilter)) { - $price = $exportFilter['group_price']; - } - } elseif ($table == ImportAdvancedPricing::TABLE_TIER_PRICE) { + if ($table == ImportAdvancedPricing::TABLE_TIER_PRICE) { $selectFields = [ ImportAdvancedPricing::COL_SKU => 'cpe.sku', ImportAdvancedPricing::COL_TIER_PRICE_WEBSITE => 'ap.website_id', diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index 6a2f7632c11f363e291a5bb7aafac9355467701e..59334a6f43164476274468ed0f82b0a16bde045e 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -32,16 +32,8 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract const COL_TIER_PRICE = 'tier_price'; - const COL_GROUP_PRICE_WEBSITE = 'group_price_website'; - - const COL_GROUP_PRICE_CUSTOMER_GROUP = 'group_price_customer_group'; - - const COL_GROUP_PRICE = 'group_price'; - const TABLE_TIER_PRICE = 'catalog_product_entity_tier_price'; - const TABLE_GROUPED_PRICE = 'catalog_product_entity_group_price'; - const DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE = '0'; const ENTITY_TYPE_CODE = 'advanced_pricing'; @@ -50,7 +42,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract const VALIDATOR_WEBSITE = 'validator_website'; - const VALIDATOR_GROUP_PRICE = 'validator_group_price'; + const VALIDATOR_TEAR_PRICE = 'validator_tear_price'; /** * Validation failure message template definitions @@ -65,9 +57,6 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract ValidatorInterface::ERROR_INVALID_TIER_PRICE_SITE => 'Tier Price data website is invalid', ValidatorInterface::ERROR_INVALID_TIER_PRICE_GROUP => 'Tier Price customer group is invalid', ValidatorInterface::ERROR_TIER_DATA_INCOMPLETE => 'Tier Price data is incomplete', - ValidatorInterface::ERROR_INVALID_GROUP_PRICE_SITE => 'Group Price data website is invalid', - ValidatorInterface::ERROR_INVALID_GROUP_PRICE_GROUP => 'Group Price customer group is invalid', - ValidatorInterface::ERROR_GROUP_PRICE_DATA_INCOMPLETE => 'Group Price data is incomplete', ValidatorInterface::ERROR_INVALID_ATTRIBUTE_DECIMAL => 'Value for \'%s\' attribute contains incorrect value, acceptable values are in decimal format', ]; @@ -90,9 +79,6 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract self::COL_TIER_PRICE_CUSTOMER_GROUP, self::COL_TIER_PRICE_QTY, self::COL_TIER_PRICE, - self::COL_GROUP_PRICE_WEBSITE, - self::COL_GROUP_PRICE_CUSTOMER_GROUP, - self::COL_GROUP_PRICE, ]; /** @@ -179,7 +165,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract * @param ImportProduct $importProduct * @param AdvancedPricing\Validator $validator * @param AdvancedPricing\Validator\Website $websiteValidator - * @param AdvancedPricing\Validator\GroupPrice $groupPriceValidator + * @param AdvancedPricing\Validator\TierPrice $tierPriceValidator * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( @@ -199,7 +185,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract ImportProduct $importProduct, AdvancedPricing\Validator $validator, AdvancedPricing\Validator\Website $websiteValidator, - AdvancedPricing\Validator\GroupPrice $groupPriceValidator + AdvancedPricing\Validator\TierPrice $tierPriceValidator ) { $this->_localeDate = $localeDate; $this->jsonHelper = $jsonHelper; @@ -215,7 +201,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract $this->_validators[self::VALIDATOR_MAIN] = $validator->init($this); $this->_oldSkus = $this->retrieveOldSkus(); $this->_validators[self::VALIDATOR_WEBSITE] = $websiteValidator; - $this->_validators[self::VALIDATOR_GROUP_PRICE] = $groupPriceValidator; + $this->_validators[self::VALIDATOR_TEAR_PRICE] = $tierPriceValidator; $this->errorAggregator = $errorAggregator; $this->_catalogProductEntity = $this->_resourceFactory->create()->getTable('catalog_product_entity'); @@ -228,7 +214,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract * Validator object getter. * * @param string $type - * @return AdvancedPricing\Validator|AdvancedPricing\Validator\Website|AdvancedPricing\Validator\GroupPrice + * @return AdvancedPricing\Validator|AdvancedPricing\Validator\Website */ protected function _getValidator($type) { @@ -333,8 +319,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract } } if ($listSku) { - $this->deleteProductTierAndGroupPrices(array_unique($listSku), self::TABLE_GROUPED_PRICE); - $this->deleteProductTierAndGroupPrices(array_unique($listSku), self::TABLE_TIER_PRICE); + $this->deleteProductTierPrices(array_unique($listSku), self::TABLE_TIER_PRICE); $this->setUpdatedAt($listSku); } return $this; @@ -367,7 +352,6 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract $listSku = []; while ($bunch = $this->_dataSourceModel->getNextBunch()) { $tierPrices = []; - $groupPrices = []; foreach ($bunch as $rowNum => $rowData) { if (!$this->validateRow($rowData, $rowNum)) { $this->addRowError(ValidatorInterface::ERROR_SKU_IS_EMPTY, $rowNum); @@ -391,33 +375,19 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract 'website_id' => $this->getWebsiteId($rowData[self::COL_TIER_PRICE_WEBSITE]) ]; } - if (!empty($rowData[self::COL_GROUP_PRICE_WEBSITE])) { - $groupPrices[$rowSku][] = [ - 'all_groups' => self::DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE, - 'customer_group_id' => $this->getCustomerGroupId( - $rowData[self::COL_GROUP_PRICE_CUSTOMER_GROUP] - ), - 'value' => $rowData[self::COL_GROUP_PRICE], - 'website_id' => $this->getWebSiteId($rowData[self::COL_GROUP_PRICE_WEBSITE]) - ]; - } } if (\Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE == $behavior) { if ($listSku) { - $this->processCountNewPrices($tierPrices, $groupPrices); - if ($this->deleteProductTierAndGroupPrices(array_unique($listSku), self::TABLE_GROUPED_PRICE) - && $this->deleteProductTierAndGroupPrices(array_unique($listSku), self::TABLE_TIER_PRICE)) { - $this->saveProductPrices($tierPrices, self::TABLE_TIER_PRICE) - ->saveProductPrices($groupPrices, self::TABLE_GROUPED_PRICE); + $this->processCountNewPrices($tierPrices); + if ($this->deleteProductTierPrices(array_unique($listSku), self::TABLE_TIER_PRICE)) { + $this->saveProductPrices($tierPrices, self::TABLE_TIER_PRICE); $this->setUpdatedAt($listSku); } } } elseif (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND == $behavior) { $this->processCountExistingPrices($tierPrices, self::TABLE_TIER_PRICE) - ->processCountExistingPrices($groupPrices, self::TABLE_GROUPED_PRICE) - ->processCountNewPrices($tierPrices, $groupPrices); - $this->saveProductPrices($tierPrices, self::TABLE_TIER_PRICE) - ->saveProductPrices($groupPrices, self::TABLE_GROUPED_PRICE); + ->processCountNewPrices($tierPrices); + $this->saveProductPrices($tierPrices, self::TABLE_TIER_PRICE); if ($listSku) { $this->setUpdatedAt($listSku); } @@ -457,13 +427,13 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract } /** - * Deletes tier prices and group prices. + * Deletes tier prices prices. * * @param array $listSku * @param string $tableName * @return bool */ - protected function deleteProductTierAndGroupPrices(array $listSku, $tableName) + protected function deleteProductTierPrices(array $listSku, $tableName) { if ($tableName && $listSku) { if (!$this->_cachedSkuToDelete) { @@ -530,7 +500,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract */ protected function getCustomerGroupId($customerGroup) { - $customerGroups = $this->_getValidator(self::VALIDATOR_GROUP_PRICE)->getCustomerGroups(); + $customerGroups = $this->_getValidator(self::VALIDATOR_TEAR_PRICE)->getCustomerGroups(); return $customerGroup == self::VALUE_ALL_GROUPS ? 0 : $customerGroups[$customerGroup]; } @@ -598,17 +568,13 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract * Count new prices * * @param array $tierPrices - * @param array $groupPrices * @return $this */ - protected function processCountNewPrices(array $tierPrices, array $groupPrices) + protected function processCountNewPrices(array $tierPrices) { foreach ($tierPrices as $productPrices) { $this->countItemsCreated += count($productPrices); } - foreach ($groupPrices as $productPrices) { - $this->countItemsCreated += count($productPrices); - } $this->countItemsCreated -= $this->countItemsUpdated; return $this; diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/GroupPrice.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/GroupPrice.php deleted file mode 100644 index d3a59957e9e5ef8fe48072db36aad7592c93409e..0000000000000000000000000000000000000000 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/GroupPrice.php +++ /dev/null @@ -1,160 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator; - -use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing; -use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; - -class GroupPrice extends \Magento\CatalogImportExport\Model\Import\Product\Validator\AbstractPrice -{ - /** - * @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver - */ - protected $storeResolver; - - /** - * @var array - */ - private $_groupPriceColumns = [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE, - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP, - AdvancedPricing::COL_GROUP_PRICE - ]; - - /** - * @param \Magento\Customer\Api\GroupRepositoryInterface $groupRepository - * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder - * @param \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver - */ - public function __construct( - \Magento\Customer\Api\GroupRepositoryInterface $groupRepository, - \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder, - \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver - ) { - $this->storeResolver = $storeResolver; - parent::__construct($groupRepository, $searchCriteriaBuilder); - } - - /** - * {@inheritdoc} - */ - public function init($context) - { - foreach ($this->groupRepository->getList($this->searchCriteriaBuilder->create())->getItems() as $group) { - $this->customerGroups[$group->getCode()] = $group->getId(); - } - $this->context = $context; - } - - /** - * @param string $attribute - * @return void - */ - protected function addDecimalError($attribute) - { - $this->_addMessages( - [ - sprintf( - $this->context->retrieveMessageTemplate( - RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_DECIMAL - ), - $attribute - ) - ] - ); - } - - /** - * @return void - */ - protected function initCustomerGroups() - { - if (!$this->customerGroups) { - $this->init($this->context); - } - } - - /** - * Validate value - * - * @param array $value - * @return bool - */ - public function isValid($value) - { - $this->_clearMessages(); - $this->initCustomerGroups(); - if (!$this->isValidValueAndLength($value)) { - return true; - } - if (!isset($value[AdvancedPricing::COL_GROUP_PRICE_WEBSITE]) - || !isset($value[AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP]) - || $this->hasEmptyColumns($value)) { - $this->_addMessages([self::ERROR_GROUP_PRICE_DATA_INCOMPLETE]); - return false; - } elseif ( - $value[AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP] == AdvancedPricing::VALUE_ALL_GROUPS - || !isset($this->customerGroups[$value[AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP]]) - ) { - $this->_addMessages([self::ERROR_INVALID_GROUP_PRICE_GROUP]); - return false; - } - if (!is_numeric($value[AdvancedPricing::COL_GROUP_PRICE]) - || $value[AdvancedPricing::COL_GROUP_PRICE] < 0 - ) { - $this->addDecimalError(AdvancedPricing::COL_GROUP_PRICE); - return false; - } - return true; - } - - /** - * Get existing customers groups - * - * @return array - */ - public function getCustomerGroups() - { - if (!$this->customerGroups) { - $this->init($this->context); - } - return $this->customerGroups; - } - - /** - * Check if at list one value and length are valid - * - * @param array $value - * @return bool - */ - protected function isValidValueAndLength(array $value) - { - $isValid = false; - foreach ($this->_groupPriceColumns as $column) { - if (isset($value[$column]) && strlen($value[$column])) { - $isValid = true; - } - } - return $isValid; - } - - /** - * Check if value has empty columns - * - * @param array $value - * @return bool - */ - protected function hasEmptyColumns(array $value) - { - $hasEmptyValues = false; - foreach ($this->_groupPriceColumns as $column) { - if (!strlen($value[$column])) { - $hasEmptyValues = true; - } - } - return $hasEmptyValues; - } -} diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php index 03ffcc962f55481a372e745b5b7d71e07427fb60..bac6a0e44d5a624cd15fc8bee26543df8459acc8 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/TierPrice.php @@ -68,6 +68,19 @@ class TierPrice extends \Magento\CatalogImportExport\Model\Import\Product\Valida ); } + /** + * Get existing customers groups + * + * @return array + */ + public function getCustomerGroups() + { + if (!$this->customerGroups) { + $this->init($this->context); + } + return $this->customerGroups; + } + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * Validation diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php index 322a5b1956816793f73d2324356bace72abed8a9..ff66ecafcc09572aa5b8fb39601acec8db5d0f7e 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator/Website.php @@ -72,9 +72,6 @@ class Website extends AbstractImportValidator implements RowValidatorInterface if (isset($value[AdvancedPricing::COL_TIER_PRICE]) && !empty($value[AdvancedPricing::COL_TIER_PRICE])) { $valid *= $this->isWebsiteValid($value, AdvancedPricing::COL_TIER_PRICE_WEBSITE); } - if (isset($value[AdvancedPricing::COL_GROUP_PRICE]) && !empty($value[AdvancedPricing::COL_GROUP_PRICE])) { - $valid *= $this->isWebsiteValid($value, AdvancedPricing::COL_GROUP_PRICE_WEBSITE); - } if (!$valid) { $this->_addMessages([self::ERROR_INVALID_WEBSITE]); } diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php index 49555974a3684b2d0c8914c9aa99b593bb3d259c..82a892d1b61810efb61f0e030fe963feb876db0f 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php @@ -414,9 +414,6 @@ class AdvancedPricingTest extends \PHPUnit_Framework_TestCase $data = [ [ 'sku' => 'simpletest', - 'group_price_website' => $webSite, - 'group_price_customer_group' => $userGroup, - 'group_price' => '100', 'tier_price_website' => $webSite, 'tier_price_customer_group' => $userGroup, 'tier_price_qty' => '2', @@ -426,9 +423,6 @@ class AdvancedPricingTest extends \PHPUnit_Framework_TestCase $this->advancedPricing->expects($this->once())->method('getExportData')->willReturn($data); $exportData = [ 'sku' => 'simpletest', - 'group_price_website' => $webSite, - 'group_price_customer_group' => $userGroup, - 'group_price' => '100', 'tier_price_website' => $webSite, 'tier_price_customer_group' => $userGroup, 'tier_price_qty' => '2', diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/GroupPriceTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/GroupPriceTest.php deleted file mode 100644 index c5acdc18a935d43577388e03f105d38712cb7a35..0000000000000000000000000000000000000000 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/GroupPriceTest.php +++ /dev/null @@ -1,334 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\AdvancedPricingImportExport\Test\Unit\Model\Import\AdvancedPricing\Validator; - -use \Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing as AdvancedPricing; - -/** - * @SuppressWarnings(PHPMD) - */ -class GroupPriceTest extends \PHPUnit_Framework_TestCase -{ - - /** - * @var \Magento\Customer\Api\GroupRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $groupRepository; - - /** - * @var \Magento\Framework\Api\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject - */ - protected $searchCriteriaBuilder; - - /** - * @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeResolver; - - /** - * @var AdvancedPricing\Validator\GroupPrice|\PHPUnit_Framework_MockObject_MockObject - */ - protected $groupPrice; - - public function setUp() - { - $this->groupRepository = $this->getMockBuilder('\Magento\Customer\Api\GroupRepositoryInterface') - ->disableOriginalConstructor() - ->setMethods(['getList']) - ->getMockForAbstractClass(); - $this->searchCriteriaBuilder = $this->getMock( - '\Magento\Framework\Api\SearchCriteriaBuilder', - [], - [], - '', - false - ); - $this->storeResolver = $this->getMock( - '\Magento\CatalogImportExport\Model\Import\Product\StoreResolver', - [], - [], - '', - false - ); - - $this->groupPrice = $this->getMock( - 'Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice', - ['isValidValueAndLength', 'hasEmptyColumns', '_addMessages'], - [ - $this->groupRepository, - $this->searchCriteriaBuilder, - $this->storeResolver, - ], - '' - ); - } - - public function testInitInternalCalls() - { - $searchCriteria = $this->getMock('Magento\Framework\Api\SearchCriteria', [], [], '', false); - $this->searchCriteriaBuilder->expects($this->any())->method('create')->willReturn($searchCriteria); - $groupSearchResult = $this->getMockForAbstractClass( - '\Magento\Customer\Api\Data\GroupSearchResultsInterface', - [], - '', - false - ); - $this->groupRepository - ->expects($this->any()) - ->method('getList') - ->with($searchCriteria) - ->willReturn($groupSearchResult); - - $groupTest = $this->getMockBuilder('\Magento\Customer\Api\Data\GroupInterface') - ->disableOriginalConstructor() - ->setMethods(['getCode', 'getId']) - ->getMockForAbstractClass(); - $groupTest->expects($this->once())->method('getCode'); - $groupTest->expects($this->any())->method('getId'); - $groups = [$groupTest]; - $groupSearchResult->expects($this->any())->method('getItems')->willReturn($groups); - - $this->groupPrice->init(null); - } - - public function testInitAddToCustomerGroups() - { - $searchCriteria = $this->getMock('Magento\Framework\Api\SearchCriteria', [], [], '', false); - $this->searchCriteriaBuilder->expects($this->any())->method('create')->willReturn($searchCriteria); - $groupSearchResult = $this->getMockForAbstractClass( - '\Magento\Customer\Api\Data\GroupSearchResultsInterface', - [], - '', - false - ); - $this->groupRepository - ->expects($this->any()) - ->method('getList') - ->with($searchCriteria) - ->willReturn($groupSearchResult); - - $groupTest = $this->getMockBuilder('\Magento\Customer\Api\Data\GroupInterface') - ->disableOriginalConstructor() - ->setMethods(['getCode', 'getId']) - ->getMockForAbstractClass(); - - $expectedCode = 'code'; - $expectedId = 'id'; - $expectedCustomerGroups = [ - $expectedCode => $expectedId, - ]; - $groupTest->expects($this->once())->method('getCode')->willReturn($expectedCode); - $groupTest->expects($this->any())->method('getId')->willReturn($expectedId); - $groups = [$groupTest]; - $groupSearchResult->expects($this->any())->method('getItems')->willReturn($groups); - $this->groupPrice->init(null); - $this->assertEquals($expectedCustomerGroups, $this->getPropertyValue($this->groupPrice, 'customerGroups')); - } - - public function testIsValidInitCall() - { - $groupPrice = $this->groupPrice = $this->getMock( - 'Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice', - ['init', '_clearMessages'], - [ - $this->groupRepository, - $this->searchCriteriaBuilder, - $this->storeResolver, - ], - '' - ); - $groupPrice->expects($this->once())->method('_clearMessages'); - $this->setPropertyValue($groupPrice, 'customerGroups', false); - $groupPrice->expects($this->once())->method('init'); - - $groupPrice->isValid([]); - } - - /** - * @dataProvider isValidResultFalseDataProvider - * - * @param array $value - * @param array $hasEmptyColumns - * @param array $customerGroups - */ - public function testIsValidResultFalse($value, $hasEmptyColumns, $customerGroups) - { - $this->groupPrice->expects($this->once())->method('isValidValueAndLength')->willReturn(true); - $this->groupPrice->expects($this->any())->method('hasEmptyColumns')->willReturn($hasEmptyColumns); - $this->setPropertyValue($this->groupPrice, 'customerGroups', $customerGroups); - - $result = $this->groupPrice->isValid($value); - $this->assertFalse($result); - } - - public function testIsValidResultTrue() - { - $this->groupPrice->expects($this->once())->method('isValidValueAndLength')->willReturn(false); - $this->setPropertyValue($this->groupPrice, 'customerGroups', true); - - $result = $this->groupPrice->isValid([]); - $this->assertTrue($result); - } - - /** - * @dataProvider isValidAddMessagesCallDataProvider - * - * @param array $value - * @param bool $hasEmptyColumns - * @param array $customerGroups - * @param array $expectedMessages - */ - public function testIsValidAddMessagesCall($value, $hasEmptyColumns, $customerGroups, $expectedMessages) - { - $this->groupPrice->expects($this->once())->method('isValidValueAndLength')->willReturn(true); - $this->groupPrice->expects($this->any())->method('hasEmptyColumns')->willReturn($hasEmptyColumns); - $this->setPropertyValue($this->groupPrice, 'customerGroups', $customerGroups); - - $this->groupPrice->expects($this->once())->method('_addMessages')->with($expectedMessages); - $this->groupPrice->isValid($value); - } - - public function testGetCustomerGroupsInitCall() - { - $groupPrice = $this->groupPrice = $this->getMock( - 'Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice', - ['init'], - [ - $this->groupRepository, - $this->searchCriteriaBuilder, - $this->storeResolver, - ], - '' - ); - $this->setPropertyValue($groupPrice, 'customerGroups', false); - $groupPrice->expects($this->once())->method('init'); - - $groupPrice->getCustomerGroups(); - } - - public function isValidResultFalseDataProvider() - { - return [ - // First if condition cases. - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => null, - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'value', - ], - '$hasEmptyColumns' => null, - '$customerGroups' => [ - 'value' => 'value' - ], - ], - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => null, - ], - '$hasEmptyColumns' => null, - '$customerGroups' => [ - 'value' - ], - ], - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'value', - ], - '$hasEmptyColumns' => true, - '$customerGroups' => [ - 'value' => 'value' - ], - ], - // Second if condition cases. - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => AdvancedPricing::VALUE_ALL_GROUPS, - ], - '$hasEmptyColumns' => false, - '$customerGroups' => [ - 'group price customer value' => 'value' - ], - ], - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'group price customer value', - ], - '$hasEmptyColumns' => false, - '$customerGroups' => [ - 'group price customer value' => null - ], - ], - ]; - } - - public function isValidAddMessagesCallDataProvider() - { - return [ - // First if condition cases. - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => null, - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'value', - AdvancedPricing::VALUE_ALL_GROUPS => 'value', - ], - '$hasEmptyColumns' => null, - '$customerGroups' => [ - 'value' => 'value' - ], - '$expectedMessages' => [AdvancedPricing\Validator::ERROR_GROUP_PRICE_DATA_INCOMPLETE], - ], - // Second if condition cases. - [ - '$value' => [ - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'value', - AdvancedPricing::VALUE_ALL_GROUPS => 'not ALL GROUPS', - ], - '$hasEmptyColumns' => false, - '$customerGroups' => [ - 'value' => null - ], - '$expectedMessages' => [AdvancedPricing\Validator::ERROR_INVALID_GROUP_PRICE_GROUP], - ], - ]; - } - - /** - * Get any object property value. - * - * @param $object - * @param $property - */ - protected function getPropertyValue($object, $property) - { - $reflection = new \ReflectionClass(get_class($object)); - $reflectionProperty = $reflection->getProperty($property); - $reflectionProperty->setAccessible(true); - - return $reflectionProperty->getValue($object); - } - - /** - * Set object property value. - * - * @param $object - * @param $property - * @param $value - */ - protected function setPropertyValue(&$object, $property, $value) - { - $reflection = new \ReflectionClass(get_class($object)); - $reflectionProperty = $reflection->getProperty($property); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($object, $value); - - return $object; - } -} diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php index a2d5480898e5d0ffd20e30ebf90dcac4a7c4328a..42cd38395955e395c701022fae5d321e2414055e 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/TierPriceTest.php @@ -132,52 +132,6 @@ class TierPriceTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedCustomerGroups, $this->getPropertyValue($this->tierPrice, 'customerGroups')); } - public function testIsValidInitCall() - { - $tierPrice = $this->tierPrice = $this->getMock( - 'Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice', - ['init', '_clearMessages'], - [ - $this->groupRepository, - $this->searchCriteriaBuilder, - $this->storeResolver, - ], - '' - ); - $tierPrice->expects($this->once())->method('_clearMessages'); - $this->setPropertyValue($tierPrice, 'customerGroups', false); - $tierPrice->expects($this->once())->method('init'); - - $tierPrice->isValid([]); - } - - /** - * @dataProvider isValidResultFalseDataProvider - * - * @param array $value - * @param bool $hasEmptyColumns - * @param array $customerGroups - */ - public function testIsValidResultFalse($value, $hasEmptyColumns, $customerGroups) - { - $tierPrice = $this->getMock( - 'Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice', - ['init', '_clearMessages', 'isValidValueAndLength', 'hasEmptyColumns'], - [ - $this->groupRepository, - $this->searchCriteriaBuilder, - $this->storeResolver, - ], - '' - ); - $tierPrice->expects($this->once())->method('isValidValueAndLength')->willReturn(true); - $tierPrice->expects($this->any())->method('hasEmptyColumns')->willReturn($hasEmptyColumns); - $this->setPropertyValue($tierPrice, 'customerGroups', $customerGroups); - - $result = $tierPrice->isValid($value); - $this->assertFalse($result); - } - public function testIsValidResultTrue() { $this->tierPrice->expects($this->once())->method('isValidValueAndLength')->willReturn(false); diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php index 2d2c33661e2576a984a97605a570a101b9bc0d90..557634d888673af410de5ebf8475c4b2534df275 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php @@ -66,21 +66,18 @@ class WebsiteTest extends \PHPUnit_Framework_TestCase * @param array $value * @param string $allWebsites * @param string $colTierPriceWebsite - * @param string $colGroupPriceWebsite * @param bool $expectedResult */ public function testIsValidReturn( $value, $allWebsites, $colTierPriceWebsite, - $colGroupPriceWebsite, $expectedResult ) { $this->website->expects($this->once())->method('_clearMessages'); $this->website->expects($this->any())->method('getAllWebsitesValue')->willReturn($allWebsites); $this->storeResolver->method('getWebsiteCodeToId')->willReturnMap([ [$value[AdvancedPricing::COL_TIER_PRICE_WEBSITE], $colTierPriceWebsite], - [$value[AdvancedPricing::COL_GROUP_PRICE_WEBSITE], $colGroupPriceWebsite], ]); $result = $this->website->isValid($value); @@ -91,18 +88,15 @@ class WebsiteTest extends \PHPUnit_Framework_TestCase { $value = [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', ]; $allWebsitesValue = 'not tier|group price website value'; $colTierPriceWebsite = false; - $colGroupPriceWebsite = 'value'; $expectedMessages = [AdvancedPricing\Validator\Website::ERROR_INVALID_WEBSITE]; $this->website->expects($this->once())->method('_clearMessages'); $this->website->expects($this->any())->method('getAllWebsitesValue')->willReturn($allWebsitesValue); $this->storeResolver->method('getWebsiteCodeToId')->willReturnMap([ [$value[AdvancedPricing::COL_TIER_PRICE_WEBSITE], $colTierPriceWebsite], - [$value[AdvancedPricing::COL_GROUP_PRICE_WEBSITE], $colGroupPriceWebsite], ]); $this->website->expects($this->any())->method('_addMessages')->with($expectedMessages); @@ -140,90 +134,52 @@ class WebsiteTest extends \PHPUnit_Framework_TestCase [ '$value' => [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', AdvancedPricing::COL_TIER_PRICE => 'value', - AdvancedPricing::COL_GROUP_PRICE => 'group value', ], '$allWebsites' => 'not tier|group price website value', '$colTierPriceWebsite' => false, - '$colGroupPriceWebsite' => 'value', '$expectedResult' => false, ], [ '$value' => [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', AdvancedPricing::COL_TIER_PRICE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE => 'value', ], '$allWebsites' => 'not tier|group price website value', '$colTierPriceWebsite' => false, - '$colGroupPriceWebsite' => 'value', - '$expectedResult' => false, - ], - [ - '$value' => [ - AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', - AdvancedPricing::COL_TIER_PRICE => 'value', - AdvancedPricing::COL_GROUP_PRICE => ' group value', - ], - '$allWebsites' => 'not tier|group price website value', - '$colTierPriceWebsite' => 'value', - '$colGroupPriceWebsite' => false, - '$expectedResult' => false, - ], - [ - '$value' => [ - AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', - AdvancedPricing::COL_TIER_PRICE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE => 'value', - ], - '$allWebsites' => 'not tier|group price website value', - '$colTierPriceWebsite' => 'value', - '$colGroupPriceWebsite' => false, '$expectedResult' => false, ], // True cases. [ '$value' => [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', ], '$allWebsites' => 'tier value', '$colTierPriceWebsite' => 'value', - '$colGroupPriceWebsite' => 'value', '$expectedResult' => true, ], [ '$value' => [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', ], '$allWebsites' => 'group value', '$colTierPriceWebsite' => 'value', - '$colGroupPriceWebsite' => 'value', '$expectedResult' => true, ], [ '$value' => [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => false, - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group value', ], '$allWebsites' => 'not tier|group price website value', '$colTierPriceWebsite' => 'value', - '$colGroupPriceWebsite' => 'value', '$expectedResult' => true, ], [ '$value' => [ AdvancedPricing::COL_TIER_PRICE_WEBSITE => 'tier value', - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => false, ], '$allWebsites' => 'not tier|group price website value', '$colTierPriceWebsite' => 'value', - '$colGroupPriceWebsite' => 'value', '$expectedResult' => true, ], ]; diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php index 4b000863e37388a3863dd372c7756f5466dff91d..7471ef81011c2956c927c1b505eb88b0c34cb392 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php @@ -52,9 +52,9 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A protected $websiteValidator; /** - * @var AdvancedPricing\Validator\GroupPrice |\PHPUnit_Framework_MockObject_MockObject + * @var AdvancedPricing\Validator\TearPrice |\PHPUnit_Framework_MockObject_MockObject */ - protected $groupPriceValidator; + protected $tierPriceValidator; /** * @var \Magento\ImportExport\Model\Resource\Helper |\PHPUnit_Framework_MockObject_MockObject @@ -234,8 +234,8 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A '', false ); - $this->groupPriceValidator = $this->getMock( - '\Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice', + $this->tierPriceValidator = $this->getMock( + '\Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\TierPrice', [], [], '', @@ -265,7 +265,7 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A 'saveProductPrices', 'getCustomerGroupId', 'getWebSiteId', - 'deleteProductTierAndGroupPrices', + 'deleteProductTierPrices', 'getBehavior', 'saveAndReplaceAdvancedPrices', 'processCountExistingPrices', @@ -406,8 +406,7 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A $groupCustomerGroupId, $tierWebsiteId, $groupWebsiteId, - $expectedTierPrices, - $expectedGroupPrices + $expectedTierPrices ) { $this->advancedPricing ->expects($this->any()) @@ -418,12 +417,10 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A $this->advancedPricing->expects($this->any())->method('getCustomerGroupId')->willReturnMap([ [$data[0][AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP], $tierCustomerGroupId], - [$data[0][AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP], $groupCustomerGroupId] ]); $this->advancedPricing->expects($this->any())->method('getWebSiteId')->willReturnMap([ [$data[0][AdvancedPricing::COL_TIER_PRICE_WEBSITE], $tierWebsiteId], - [$data[0][AdvancedPricing::COL_GROUP_PRICE_WEBSITE], $groupWebsiteId] ]); $this->advancedPricing->expects($this->any())->method('saveProductPrices')->will($this->returnSelf()); @@ -448,7 +445,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A ], ]; $expectedTierPrices = []; - $expectedGroupPrices = []; $listSku = [ $skuVal ]; @@ -467,12 +463,8 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A $this->advancedPricing ->expects($this->any()) - ->method('deleteProductTierAndGroupPrices') + ->method('deleteProductTierPrices') ->withConsecutive( - [ - $listSku, - AdvancedPricing::TABLE_GROUPED_PRICE - ], [ $listSku, AdvancedPricing::TABLE_TIER_PRICE, @@ -487,10 +479,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A [ $expectedTierPrices, AdvancedPricing::TABLE_TIER_PRICE - ], - [ - $expectedGroupPrices, - AdvancedPricing::TABLE_GROUPED_PRICE, ] ) ->will($this->returnSelf()); @@ -518,10 +506,9 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A $this->advancedPricing->expects($this->any())->method('validateRow')->willReturn(true); $expectedSkuList = ['sku value']; $this->advancedPricing - ->expects($this->exactly(2)) - ->method('deleteProductTierAndGroupPrices') + ->expects($this->once()) + ->method('deleteProductTierPrices') ->withConsecutive( - [$expectedSkuList, AdvancedPricing::TABLE_GROUPED_PRICE], [$expectedSkuList, AdvancedPricing::TABLE_TIER_PRICE] )->will($this->returnSelf()); @@ -574,10 +561,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups ', AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', - //group - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group price website value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'group price customer group value - not all groups ', - AdvancedPricing::COL_GROUP_PRICE => 'group price value', ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -595,16 +578,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A ], ], ], - '$expectedGroupPrices' => [ - 'sku value' => [ - [ - 'all_groups' => AdvancedPricing::DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE, - 'customer_group_id' => 'group customer group id value',//$groupCustomerGroupId - 'value' => 'group price value', - 'website_id' => 'group website id value', - ], - ], - ], ], [// tier customer group is equal to all group '$data' => [ @@ -615,10 +588,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => AdvancedPricing::VALUE_ALL_GROUPS, AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', - //group - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group price website value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'group price customer group value', - AdvancedPricing::COL_GROUP_PRICE => 'group price value', ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -636,16 +605,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A ], ], ], - '$expectedGroupPrices' => [ - 'sku value' => [ - [ - 'all_groups' => AdvancedPricing::DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE, - 'customer_group_id' => 'group customer group id value',//$groupCustomerGroupId - 'value' => 'group price value', - 'website_id' => 'group website id value', - ], - ], - ], ], [ '$data' => [ @@ -656,10 +615,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups', AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', - //group - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => 'group price website value', - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'group price customer group value', - AdvancedPricing::COL_GROUP_PRICE => 'group price value', ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -667,16 +622,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A '$tierWebsiteId' => 'tier website id value', '$groupWebsiteId' => 'group website id value', '$expectedTierPrices' => [], - '$expectedGroupPrices' => [ - 'sku value' => [ - [ - 'all_groups' => AdvancedPricing::DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE, - 'customer_group_id' => 'group customer group id value',//$groupCustomerGroupId - 'value' => 'group price value', - 'website_id' => 'group website id value', - ], - ], - ], ], [ '$data' => [ @@ -687,10 +632,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A AdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'tier price customer group value - not all groups', AdvancedPricing::COL_TIER_PRICE_QTY => 'tier price qty value', AdvancedPricing::COL_TIER_PRICE => 'tier price value', - //group - AdvancedPricing::COL_GROUP_PRICE_WEBSITE => null, - AdvancedPricing::COL_GROUP_PRICE_CUSTOMER_GROUP => 'group price customer group value', - AdvancedPricing::COL_GROUP_PRICE => 'group price value', ], ], '$tierCustomerGroupId' => 'tier customer group id value', @@ -708,7 +649,6 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A ], ] ], - '$expectedGroupPrices' => [], ], ]; // @codingStandardsIgnoreEnd @@ -851,7 +791,7 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A $this->importProduct, $this->validator, $this->websiteValidator, - $this->groupPriceValidator + $this->tierPriceValidator ], '' ); diff --git a/app/code/Magento/AdvancedPricingImportExport/etc/di.xml b/app/code/Magento/AdvancedPricingImportExport/etc/di.xml index cf59e93023c01dbe21378ccf571b7a9de14d8efa..9871308ef1a357812242dc878d693314edb8403d 100644 --- a/app/code/Magento/AdvancedPricingImportExport/etc/di.xml +++ b/app/code/Magento/AdvancedPricingImportExport/etc/di.xml @@ -12,7 +12,6 @@ <type name="Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator"> <arguments> <argument name="validators" xsi:type="array"> - <item name="groupPrice" xsi:type="object">Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice</item> <item name="tierPrice" xsi:type="object">Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\TierPrice</item> <item name="website" xsi:type="object">Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\Website</item> </argument> diff --git a/app/code/Magento/Authorizenet/Model/Authorizenet.php b/app/code/Magento/Authorizenet/Model/Authorizenet.php index 745d3b048125514eaa0c5f8965da0ef2ad98642c..c97dd05be20ce82ba5abf7d610a4148bc28d9568 100644 --- a/app/code/Magento/Authorizenet/Model/Authorizenet.php +++ b/app/code/Magento/Authorizenet/Model/Authorizenet.php @@ -5,6 +5,8 @@ */ namespace Magento\Authorizenet\Model; +use Magento\Payment\Model\Method\Logger; + /** * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) @@ -54,6 +56,8 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc const RESPONSE_REASON_CODE_PENDING_REVIEW_DECLINED = 254; + const PAYMENT_UPDATE_STATUS_CODE_SUCCESS = 'Ok'; + /** * Transaction fraud state key */ @@ -95,6 +99,16 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc */ protected $transactionDetails = []; + /** + * {@inheritdoc} + */ + protected $_debugReplacePrivateDataKeys = ['merchantAuthentication', 'x_login']; + + /** + * @var \Magento\Framework\Xml\Security + */ + protected $xmlSecurityHelper; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -108,6 +122,7 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc * @param \Magento\Authorizenet\Helper\Data $dataHelper * @param \Magento\Authorizenet\Model\Request\Factory $requestFactory * @param \Magento\Authorizenet\Model\Response\Factory $responseFactory + * @param \Magento\Framework\Xml\Security $xmlSecurityHelper * @param \Magento\Framework\Model\Resource\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data @@ -126,6 +141,7 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc \Magento\Authorizenet\Helper\Data $dataHelper, \Magento\Authorizenet\Model\Request\Factory $requestFactory, \Magento\Authorizenet\Model\Response\Factory $responseFactory, + \Magento\Framework\Xml\Security $xmlSecurityHelper, \Magento\Framework\Model\Resource\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] @@ -133,6 +149,7 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc $this->dataHelper = $dataHelper; $this->requestFactory = $requestFactory; $this->responseFactory = $responseFactory; + $this->xmlSecurityHelper = $xmlSecurityHelper; parent::__construct( $context, @@ -364,11 +381,11 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc */ protected function postRequest(\Magento\Authorizenet\Model\Request $request) { - $debugData = ['request' => $request->getData()]; $result = $this->responseFactory->create(); $client = new \Magento\Framework\HTTP\ZendClient(); - $uri = $this->getConfigData('cgi_url'); - $client->setUri($uri ? $uri : self::CGI_URL); + $url = $this->getConfigData('cgi_url') ?: self::CGI_URL; + $debugData = ['url' => $url, 'request' => $request->getData()]; + $client->setUri($url); $client->setConfig(['maxredirects' => 0, 'timeout' => 30]); foreach ($request->getData() as $key => $value) { @@ -381,21 +398,21 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc try { $response = $client->request(); + $responseBody = $response->getBody(); + $debugData['response'] = $responseBody; } catch (\Exception $e) { $result->setXResponseCode(-1) ->setXResponseReasonCode($e->getCode()) ->setXResponseReasonText($e->getMessage()); - $debugData['result'] = $result->getData(); - $this->_debug($debugData); throw new \Magento\Framework\Exception\LocalizedException( $this->dataHelper->wrapGatewayError($e->getMessage()) ); + } finally { + $this->_debug($debugData); } - $responseBody = $response->getBody(); $r = explode(self::RESPONSE_DELIM_CHAR, $responseBody); - if ($r) { $result->setXResponseCode((int)str_replace('"', '', $r[0])) ->setXResponseReasonCode((int)str_replace('"', '', $r[2])) @@ -413,10 +430,6 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc __('Something went wrong in the payment gateway.') ); } - - $debugData['result'] = $result->getData(); - $this->_debug($debugData); - return $result; } @@ -473,24 +486,39 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc ); $client = new \Magento\Framework\HTTP\ZendClient(); - $uri = $this->getConfigData('cgi_url_td'); - $client->setUri($uri ? $uri : self::CGI_URL_TD); + $url = $this->getConfigData('cgi_url_td') ?: self::CGI_URL_TD; + $client->setUri($url); $client->setConfig(['timeout' => 45]); $client->setHeaders(['Content-Type: text/xml']); $client->setMethod(\Zend_Http_Client::POST); $client->setRawData($requestBody); - $debugData = ['request' => $requestBody]; + $debugData = ['url' => $url, 'request' => $this->removePrivateDataFromXml($requestBody)]; try { $responseBody = $client->request()->getBody(); - $debugData['result'] = $responseBody; - $this->_debug($debugData); + if (!$this->xmlSecurityHelper->scan($responseBody)) { + $this->_logger->critical('Attempt loading of external XML entities in response from Authorizenet.'); + throw new \Exception(); + } + $debugData['response'] = $responseBody; libxml_use_internal_errors(true); $responseXmlDocument = new \Magento\Framework\Simplexml\Element($responseBody); libxml_use_internal_errors(false); } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException(__('Payment updating error.')); + throw new \Magento\Framework\Exception\LocalizedException( + __('Unable to get transaction details. Try again later.') + ); + } finally { + $this->_debug($debugData); + } + + if (!isset($responseXmlDocument->messages->resultCode) + || $responseXmlDocument->messages->resultCode != static::PAYMENT_UPDATE_STATUS_CODE_SUCCESS + ) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Unable to get transaction details. Try again later.') + ); } $this->transactionDetails[$transactionId] = $responseXmlDocument; @@ -509,4 +537,20 @@ abstract class Authorizenet extends \Magento\Payment\Model\Method\Cc ? $this->transactionDetails[$transactionId] : $this->loadTransactionDetails($transactionId); } + + /** + * Remove nodes with private data from XML string + * + * Uses values from $_debugReplacePrivateDataKeys property + * + * @param string $xml + * @return string + */ + protected function removePrivateDataFromXml($xml) + { + foreach ($this->getDebugReplacePrivateDataKeys() as $key) { + $xml = preg_replace(sprintf('~(?<=<%s>).*?(?=</%s>)~', $key, $key), Logger::DEBUG_KEYS_MASK, $xml); + } + return $xml; + } } diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index 71e81e1727e8b24259510278b2ae6bb1e737293b..fe98d75ecc06aed173d0ace27d475810b60ebb1d 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -130,6 +130,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra * @param \Magento\Authorizenet\Helper\Data $dataHelper * @param \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory * @param \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory + * @param \Magento\Framework\Xml\Security $xmlSecurityHelper * @param \Magento\Sales\Model\OrderFactory $orderFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Quote\Model\QuoteRepository $quoteRepository @@ -153,6 +154,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra \Magento\Authorizenet\Helper\Data $dataHelper, \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory, \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory, + \Magento\Framework\Xml\Security $xmlSecurityHelper, \Magento\Sales\Model\OrderFactory $orderFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Quote\Model\QuoteRepository $quoteRepository, @@ -183,6 +185,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra $dataHelper, $requestFactory, $responseFactory, + $xmlSecurityHelper, $resource, $resourceCollection, $data @@ -726,17 +729,21 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra */ protected function processPaymentFraudStatus(\Magento\Sales\Model\Order\Payment $payment) { - $fraudDetailsResponse = $payment->getMethodInstance() - ->fetchTransactionFraudDetails($this->getResponse()->getXTransId()); - $fraudData = $fraudDetailsResponse->getData(); + try { + $fraudDetailsResponse = $payment->getMethodInstance() + ->fetchTransactionFraudDetails($this->getResponse()->getXTransId()); + $fraudData = $fraudDetailsResponse->getData(); - if (empty($fraudData)) { - $payment->setIsFraudDetected(false); - return $this; - } + if (empty($fraudData)) { + $payment->setIsFraudDetected(false); + return $this; + } - $payment->setIsFraudDetected(true); - $payment->setAdditionalInformation('fraud_details', $fraudData); + $payment->setIsFraudDetected(true); + $payment->setAdditionalInformation('fraud_details', $fraudData); + } catch (\Exception $e) { + //this request is optional + } return $this; } @@ -749,23 +756,27 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra */ protected function addStatusComment(\Magento\Sales\Model\Order\Payment $payment) { - $transactionId = $this->getResponse()->getXTransId(); - $data = $payment->getMethodInstance()->getTransactionDetails($transactionId); - $transactionStatus = (string)$data->transaction->transactionStatus; - $fdsFilterAction = (string)$data->transaction->FDSFilterAction; - - if ($payment->getIsTransactionPending()) { - $message = 'Amount of %1 is pending approval on the gateway.<br/>' - . 'Transaction "%2" status is "%3".<br/>' - . 'Transaction FDS Filter Action is "%4"'; - $message = __( - $message, - $payment->getOrder()->getBaseCurrency()->formatTxt($this->getResponse()->getXAmount()), - $transactionId, - $this->dataHelper->getTransactionStatusLabel($transactionStatus), - $this->dataHelper->getFdsFilterActionLabel($fdsFilterAction) - ); - $payment->getOrder()->addStatusHistoryComment($message); + try { + $transactionId = $this->getResponse()->getXTransId(); + $data = $payment->getMethodInstance()->getTransactionDetails($transactionId); + $transactionStatus = (string)$data->transaction->transactionStatus; + $fdsFilterAction = (string)$data->transaction->FDSFilterAction; + + if ($payment->getIsTransactionPending()) { + $message = 'Amount of %1 is pending approval on the gateway.<br/>' + . 'Transaction "%2" status is "%3".<br/>' + . 'Transaction FDS Filter Action is "%4"'; + $message = __( + $message, + $payment->getOrder()->getBaseCurrency()->formatTxt($this->getResponse()->getXAmount()), + $transactionId, + $this->dataHelper->getTransactionStatusLabel($transactionStatus), + $this->dataHelper->getFdsFilterActionLabel($fdsFilterAction) + ); + $payment->getOrder()->addStatusHistoryComment($message); + } + } catch (\Exception $e) { + //this request is optional } return $this; } diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml index cfff3a927453c6ff3125de5589db2e8c772767c1..7dc326ff9c56954251e0834fca20874d5835522f 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml @@ -45,7 +45,7 @@ <label>Gateway URL</label> </field> <field id="cgi_url_td" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Transaction Details Url</label> + <label>Transaction Details URL</label> </field> <field id="currency" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Accepted Currency</label> diff --git a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js index 024cbb09ef952f4d85ae3d7c5a6b0ecc8758b25f..5d310915a42a2ae4e8d923f831f7af2159f0fce7 100644 --- a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js +++ b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js @@ -48,7 +48,7 @@ define( var self = this; if (this.validateHandler() && additionalValidators.validate()) { this.isPlaceOrderActionAllowed(false); - $.when(setPaymentInformationAction(this.messageContainer)).done(function() { + $.when(setPaymentInformationAction(this.messageContainer, {'method': self.getCode()})).done(function() { self.placeOrderHandler(); }).fail(function() { self.isPlaceOrderActionAllowed(true); diff --git a/app/code/Magento/Backend/Block/Store/Switcher.php b/app/code/Magento/Backend/Block/Store/Switcher.php index 8cf98be294f97b035be762e0b8f3b6d21b2a7b37..cd5c0a2247f10e1cb1f2ca1681a0085c38fda455 100644 --- a/app/code/Magento/Backend/Block/Store/Switcher.php +++ b/app/code/Magento/Backend/Block/Store/Switcher.php @@ -548,11 +548,11 @@ class Switcher extends \Magento\Backend\Block\Template $html = ''; $url = $this->getHintUrl(); if ($url) { - $html = '<div class="tooltip">' . '<span class="help"><a' . ' href="' . $this->escapeUrl( + $html = '<div class="admin__field-tooltip tooltip">' . '<a' . ' href="' . $this->escapeUrl( $url ) . '"' . ' onclick="this.target=\'_blank\'"' . ' title="' . __( 'What is this?' - ) . '"' . ' class="link-store-scope"><span>' . __( + ) . '"' . ' class="admin__field-tooltip-action action-help"><span>' . __( 'What is this?' ) . '</span></a></span>' . ' </div>'; } diff --git a/app/code/Magento/Backend/Block/Widget/Grid.php b/app/code/Magento/Backend/Block/Widget/Grid.php index ee8b36cdea847789cdde5e7876e7d5609a586848..66bc193719784b684d6b7b212790aa49686b831e 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid.php +++ b/app/code/Magento/Backend/Block/Widget/Grid.php @@ -760,7 +760,7 @@ class Grid extends \Magento\Backend\Block\Widget */ public function getJsObjectName() { - return $this->getId() . 'JsObject'; + return preg_replace("~[^a-z0-9_]*~i", '', $this->getId()) . 'JsObject'; } /** diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/AbstractFilter.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/AbstractFilter.php index 0a0e612eab9ced6d7cdcaa0715cc28a9f4b6f535..a512da3522c4229e7b41114a2559879f4f5993a7 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/AbstractFilter.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/AbstractFilter.php @@ -67,7 +67,7 @@ class AbstractFilter extends \Magento\Backend\Block\AbstractBlock implements */ protected function _getHtmlName() { - return $this->getColumn()->getId(); + return $this->escapeHtml($this->getColumn()->getId()); } /** @@ -77,7 +77,7 @@ class AbstractFilter extends \Magento\Backend\Block\AbstractBlock implements */ protected function _getHtmlId() { - return $this->getColumn()->getHtmlId(); + return $this->escapeHtml($this->getColumn()->getHtmlId()); } /** @@ -88,7 +88,7 @@ class AbstractFilter extends \Magento\Backend\Block\AbstractBlock implements */ public function getEscapedValue($index = null) { - return htmlspecialchars((string)$this->getValue($index)); + return $this->escapeHtml((string)$this->getValue($index)); } /** diff --git a/app/code/Magento/Backend/Service/V1/ModuleServiceInterface.php b/app/code/Magento/Backend/Service/V1/ModuleServiceInterface.php index 9d5d803a36a17d404e2af64b98c4d33f6f90f6aa..45933a6a6b6e25f407eb4dfa196ade2500fcb9b4 100644 --- a/app/code/Magento/Backend/Service/V1/ModuleServiceInterface.php +++ b/app/code/Magento/Backend/Service/V1/ModuleServiceInterface.php @@ -6,6 +6,9 @@ namespace Magento\Backend\Service\V1; +/** + * Interface for module service. + */ interface ModuleServiceInterface { /** diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a59b23f8d3ca722854983f16833b773dd432f87a --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Filter/TextTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Backend\Test\Unit\Block\Widget\Grid\Column\Filter; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class TextTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\Backend\Block\Widget\Grid\Column\Filter\Text*/ + protected $block; + + /** @var ObjectManagerHelper */ + protected $objectManagerHelper; + + /** @var \Magento\Backend\Block\Context|\PHPUnit_Framework_MockObject_MockObject */ + protected $context; + + /** @var \Magento\Framework\DB\Helper|\PHPUnit_Framework_MockObject_MockObject */ + protected $helper; + + /** @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject */ + protected $escaper; + + protected function setUp() + { + $this->context = $this->getMockBuilder('Magento\Backend\Block\Context') + ->setMethods(['getEscaper']) + ->disableOriginalConstructor() + ->getMock(); + $this->escaper = $this->getMock('Magento\Framework\Escaper', ['escapeHtml'], [], '', false); + $this->helper = $this->getMock('Magento\Framework\DB\Helper', [], [], '', false); + + $this->context->expects($this->once())->method('getEscaper')->willReturn($this->escaper); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->block = $this->objectManagerHelper->getObject( + 'Magento\Backend\Block\Widget\Grid\Column\Filter\Text', + [ + 'context' => $this->context, + 'resourceHelper' => $this->helper + ] + ); + } + + public function testGetHtml() + { + $resultHtml = '<input type="text" name="escapedHtml" ' . + 'id="escapedHtml" value="escapedHtml" ' . + 'class="input-text admin__control-text no-changes" data-ui-id="filter-escapedhtml" />'; + + $column = $this->getMockBuilder('Magento\Backend\Block\Widget\Grid\Column') + ->setMethods(['getId', 'getHtmlId']) + ->disableOriginalConstructor() + ->getMock(); + + $this->block->setColumn($column); + + $this->escaper->expects($this->any())->method('escapeHtml')->willReturn('escapedHtml'); + $column->expects($this->any())->method('getId')->willReturn('id'); + $column->expects($this->once())->method('getHtmlId')->willReturn('htmlId'); + + $this->assertEquals($resultHtml, $this->block->getHtml()); + } +} diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 162aba1cfa3ef6fd6d86ada42171e834be6bcb80..e755e364cde87cdb9b7b04ad6b5d7e86ee8eb35b 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -142,13 +142,17 @@ <label>Developer</label> <tab>advanced</tab> <resource>Magento_Backend::dev</resource> - <group id="debug" translate="label" type="text" sortOrder="20" showInDefault="0" showInWebsite="1" showInStore="1"> + <group id="debug" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Debug</label> - <field id="template_hints" translate="label" type="select" sortOrder="20" showInDefault="0" showInWebsite="1" showInStore="1"> - <label>Template Path Hints</label> + <field id="template_hints_storefront" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Enabled Template Path Hints for Storefront</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="template_hints_blocks" translate="label" type="select" sortOrder="21" showInDefault="0" showInWebsite="1" showInStore="1"> + <field id="template_hints_admin" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Enabled Template Path Hints for Admin</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + <field id="template_hints_blocks" translate="label" type="select" sortOrder="21" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Add Block Names to Hints</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml index 64d4ca68e905c16d57a38eba5e5217d28c8487cd..2a58b939448921c97c15ddd34ce8cd2a45b07ed5 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset.phtml @@ -13,13 +13,13 @@ <?php endif; ?> <?php if (!$_element->getNoContainer()): ?> - <fieldset class="fieldset <?php /* @escapeNotVerified */ echo $_element->getClass() ?>" id="<?php echo $_element->getHtmlId() ?>"> + <fieldset class="admin__fieldset fieldset <?php /* @escapeNotVerified */ echo $_element->getClass() ?>" id="<?php echo $_element->getHtmlId() ?>"> <?php endif; ?> <?php if ($_element->getLegend()): ?> - <legend class="legend"> + <legend class="admin__legend legend"> <span><?php /* @escapeNotVerified */ echo $_element->getLegend() ?></span> - </legend> + </legend><br/> <?php /* @escapeNotVerified */ echo $_element->getHeaderBar() ?> <?php endif; ?> <?php echo $block->getHintHtml() ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml index 24369768eb08847bb6a9637090e7245c1affa96f..22661e67952d985e33c8a1b296fdfec292a052e9 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher/form/renderer/fieldset/element.phtml @@ -14,10 +14,10 @@ $note = $element->getNote() ? '<div class="note">' . $element->getNote() . '</di $elementBeforeLabel = $element->getExtType() == 'checkbox' || $element->getExtType() == 'radio'; $addOn = $element->getBeforeElementHtml() || $element->getAfterElementHtml(); $fieldId = ($element->getHtmlContainerId()) ? ' id="' . $element->getHtmlContainerId() . '"' : ''; -$fieldClass = "field field-{$element->getId()} {$element->getCssClass()}"; +$fieldClass = "admin__field field field-{$element->getId()} {$element->getCssClass()}"; $fieldClass .= ($elementBeforeLabel) ? ' choice' : ''; $fieldClass .= ($addOn) ? ' with-addon' : ''; -$fieldClass .= ($element->getRequired()) ? ' required' : ''; +$fieldClass .= ($element->getRequired()) ? ' _required' : ''; $fieldClass .= ($note) ? ' with-note' : ''; $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' @@ -35,8 +35,9 @@ $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' <?php /* @escapeNotVerified */ echo $note ?> <?php else: ?> <?php echo $element->getLabelHtml() ?> - <div class="control"> + <div class="admin__field-control control"> <?php /* @escapeNotVerified */ echo($addOn) ? '<div class="addon">' . $element->getElementHtml() . '</div>' : $element->getElementHtml(); ?> + <?php echo $block->getHintHtml() ?> <?php /* @escapeNotVerified */ echo $note ?> </div> <?php endif; ?> @@ -44,7 +45,6 @@ $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' <div class="field-service" value-scope="<?php /* @escapeNotVerified */ echo $element->getScopeLabel()?>"> </div> <?php endif;?> - <?php echo $block->getHintHtml() ?> </div> <?php endif; ?> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml index 620c8195006dac454293a53882707eabe96bbc8f..b8c14ed801a4b3a57d3eace5078a579f18c265df 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset.phtml @@ -57,7 +57,7 @@ if ($isField) { <?php if (!$element->getNoContainer()): ?> <fieldset class="<?php /* @escapeNotVerified */ echo $cssClass ?>" id="<?php /* @escapeNotVerified */ echo $id ?>"> <?php if ($element->getLegend() && !$isWrapped): ?> - <legend class="<?php /* @escapeNotVerified */ echo $isField ? 'label' : 'legend'?>"> + <legend class="<?php /* @escapeNotVerified */ echo $isField ? 'label admin__field-label' : 'admin__legend legend'?>"> <span><?php /* @escapeNotVerified */ echo $element->getLegend() ?></span> </legend><br /> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml index 824c0e92d6cf8a3b50e85faee6dadfdc23909259..b09be9491f003ffc98c7657c283030f7f6640a4e 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/renderer/fieldset/element.phtml @@ -22,7 +22,8 @@ $fieldClass .= ($note) ? ' with-note' : ''; $fieldClass .= (!$element->getLabelHtml()) ? ' no-label' : ''; $fieldAttributes = $fieldId . ' class="' . $fieldClass . '" ' - . $block->getUiId('form-field', $element->getId()); + . $block->getUiId('form-field', $element->getId()) + . ($element->getFieldExtraAttributes() ? ' ' . $element->getFieldExtraAttributes() : ''); ?> <?php if (!$element->getNoDisplay()): ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml index 12dcacd1a035648d05e278c7da386e971d7686a6..6e32f5eed660512ef634b71aceb5443e08ee48a1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid.phtml @@ -24,7 +24,7 @@ $numColumns = sizeof($block->getColumns()); <?php if ($block->getCollection()): ?> <?php if ($block->canDisplayContainer()): ?> -<div id="<?php /* @escapeNotVerified */ echo $block->getId() ?>" data-grid-id="<?php /* @escapeNotVerified */ echo $block->getId() ?>"> +<div id="<?php echo $block->escapeHtml($block->getId()) ?>" data-grid-id="<?php echo $block->escapeHtml($block->getId()) ?>"> <?php else: ?> <?php echo $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?> <?php endif; ?> @@ -50,17 +50,17 @@ $numColumns = sizeof($block->getColumns()); <?php endif; ?> <?php $countRecords = $block->getCollection()->getSize(); ?> <div class="admin__control-support-text"> - <span id="<?php echo $block->getHtmlId() ?>-total-count" <?php /* @escapeNotVerified */ echo $block->getUiId('total-count') ?>> + <span id="<?php echo $block->escapeHtml($block->getHtmlId()) ?>-total-count" <?php /* @escapeNotVerified */ echo $block->getUiId('total-count') ?>> <?php /* @escapeNotVerified */ echo $countRecords ?> </span> <?php /* @escapeNotVerified */ echo __('records found') ?> - <span id="<?php echo $block->getHtmlId() ?>_massaction-count" + <span id="<?php echo $block->escapeHtml($block->getHtmlId()) ?>_massaction-count" class="mass-select-info _empty"><strong data-role="counter">0</strong> <span><?php /* @escapeNotVerified */ echo __('selected') ?></span></span> </div> <?php if ($block->getPagerVisibility()): ?> <div class="admin__data-grid-pager-wrap"> <select name="<?php /* @escapeNotVerified */ echo $block->getVarNameLimit() ?>" - id="<?php echo $block->getHtmlId()?>_page-limit" + id="<?php echo $block->escapeHtml($block->getHtmlId())?>_page-limit" onchange="<?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.loadByElement(this)" <?php /* @escapeNotVerified */ echo $block->getUiId('per-page') ?> class="admin__control-select"> <option value="20"<?php if ($block->getCollection()->getPageSize() == 20): ?> @@ -79,7 +79,7 @@ $numColumns = sizeof($block->getColumns()); selected="selected"<?php endif; ?>>200 </option> </select> - <label for="<?php echo $block->getHtmlId()?>_page-limit" + <label for="<?php echo $block->escapeHtml($block->getHtmlId())?>_page-limit" class="admin__control-support-text"><?php /* @escapeNotVerified */ echo __('per page') ?></label> <div class="admin__data-grid-pager"> <?php $_curPage = $block->getCollection()->getCurPage() ?> @@ -96,13 +96,13 @@ $numColumns = sizeof($block->getColumns()); <?php endif; ?> <input type="text" - id="<?php echo $block->getHtmlId()?>_page-current" + id="<?php echo $block->escapeHtml($block->getHtmlId())?>_page-current" name="<?php /* @escapeNotVerified */ echo $block->getVarNamePage() ?>" value="<?php /* @escapeNotVerified */ echo $_curPage ?>" class="admin__control-text" onkeypress="<?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.inputPage(event, '<?php /* @escapeNotVerified */ echo $_lastPage ?>')" <?php /* @escapeNotVerified */ echo $block->getUiId('current-page') ?> /> - <label class="admin__control-support-text" for="<?php echo $block->getHtmlId() + <label class="admin__control-support-text" for="<?php echo $block->escapeHtml($block->getHtmlId()) ?>_page-current"> <?php /* @escapeNotVerified */ echo __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> </label> @@ -122,13 +122,13 @@ $numColumns = sizeof($block->getColumns()); </div> <div class="admin__data-grid-wrap admin__data-grid-wrap-static"> <?php if ($block->getGridCssClass()): ?> - <table class="<?php /* @escapeNotVerified */ echo $block->getGridCssClass() ?> data-grid" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>_table"> + <table class="<?php /* @escapeNotVerified */ echo $block->getGridCssClass() ?> data-grid" id="<?php echo $block->escapeHtml($block->getId()) ?>_table"> <!-- Rendering column set --> <?php echo $block->getChildHtml('grid.columnSet'); ?> </table> <?php else: ?> - <table class="data-grid" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>_table"> + <table class="data-grid" id="<?php echo $block->escapeHtml($block->getId()) ?>_table"> <!-- Rendering column set --> <?php echo $block->getChildHtml('grid.columnSet'); ?> </table> @@ -161,7 +161,7 @@ $numColumns = sizeof($block->getColumns()); registry.get('<?php /* @escapeNotVerified */ echo $block->getDependencyJsObject() ?>', function (<?php /* @escapeNotVerified */ echo $block->getDependencyJsObject() ?>) { <?php endif; ?> - <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?> = new varienGrid('<?php /* @escapeNotVerified */ echo $block->getId() ?>', '<?php /* @escapeNotVerified */ echo $block->getGridUrl() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNamePage() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameSort() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameDir() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameFilter() ?>'); + <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?> = new varienGrid('<?php echo $block->escapeHtml($block->getId()) ?>', '<?php /* @escapeNotVerified */ echo $block->getGridUrl() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNamePage() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameSort() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameDir() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameFilter() ?>'); <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.useAjax = <?php /* @escapeNotVerified */ echo $block->getUseAjax() ? 'true' : 'false' ?>; <?php if ($block->getRowClickCallback()): ?> <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.rowClickCallback = <?php /* @escapeNotVerified */ echo $block->getRowClickCallback() ?>; diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml index 385a5a1393c16698ed543ce292d3e18c398ca8ec..ddd9fa5f2f18ae565d365c090d2ad2c8dd35f55a 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml @@ -26,7 +26,7 @@ $numColumns = sizeof($block->getColumns()); <?php if ($block->getCollection()): ?> <?php if ($block->canDisplayContainer()): ?> - <div id="<?php /* @escapeNotVerified */ echo $block->getId() ?>" data-grid-id="<?php /* @escapeNotVerified */ echo $block->getId() ?>"> + <div id="<?php echo $block->escapeHtml($block->getId()) ?>" data-grid-id="<?php echo $block->escapeHtml($block->getId()) ?>"> <?php else: ?> <?php echo $block->getLayout()->getMessagesBlock()->getGroupedHtml() ?> <?php endif; ?> @@ -41,8 +41,8 @@ $numColumns = sizeof($block->getColumns()); <div class="admin__data-grid-export"> <label class="admin__control-support-text" - for="<?php /* @escapeNotVerified */ echo $block->getId() ?>_export"><?php /* @escapeNotVerified */ echo __('Export to:') ?></label> - <select name="<?php /* @escapeNotVerified */ echo $block->getId() ?>_export" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>_export" + for="<?php echo $block->escapeHtml($block->getId()) ?>_export"><?php /* @escapeNotVerified */ echo __('Export to:') ?></label> + <select name="<?php echo $block->escapeHtml($block->getId()) ?>_export" id="<?php echo $block->escapeHtml($block->getId()) ?>_export" class="admin__control-select"> <?php foreach ($block->getExportTypes() as $_type): ?> <option value="<?php /* @escapeNotVerified */ echo $_type->getUrl() ?>"><?php /* @escapeNotVerified */ echo $_type->getLabel() ?></option> @@ -61,18 +61,18 @@ $numColumns = sizeof($block->getColumns()); <?php endif; ?> <?php $countRecords = $block->getCollection()->getSize(); ?> <div class="admin__control-support-text"> - <span id="<?php echo $block->getHtmlId() ?>-total-count" <?php /* @escapeNotVerified */ echo $block->getUiId('total-count') ?>> + <span id="<?php echo $block->escapeHtml($block->getHtmlId()) ?>-total-count" <?php /* @escapeNotVerified */ echo $block->getUiId('total-count') ?>> <?php /* @escapeNotVerified */ echo $countRecords ?> </span> <?php /* @escapeNotVerified */ echo __('records found') ?> - <span id="<?php echo $block->getHtmlId() ?>_massaction-count" + <span id="<?php echo $block->escapeHtml($block->getHtmlId()) ?>_massaction-count" class="mass-select-info _empty"><strong data-role="counter">0</strong> <span><?php /* @escapeNotVerified */ echo __('selected') ?></span></span> </div> <?php if ($block->getPagerVisibility()): ?> <div class="admin__data-grid-pager-wrap"> <select name="<?php /* @escapeNotVerified */ echo $block->getVarNameLimit() ?>" - id="<?php echo $block->getHtmlId()?>_page-limit" + id="<?php echo $block->escapeHTML($block->getHtmlId())?>_page-limit" onchange="<?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.loadByElement(this)" class="admin__control-select"> <option value="20"<?php if ($block->getCollection()->getPageSize() == 20): ?> @@ -91,7 +91,7 @@ $numColumns = sizeof($block->getColumns()); selected="selected"<?php endif; ?>>200 </option> </select> - <label for="<?php echo $block->getHtmlId()?><?php echo $block->getHtmlId()?>_page-limit" + <label for="<?php echo $block->escapeHTML($block->getHtmlId())?><?php echo $block->escapeHTML($block->getHtmlId())?>_page-limit" class="admin__control-support-text"><?php /* @escapeNotVerified */ echo __('per page') ?></label> <div class="admin__data-grid-pager"> @@ -107,12 +107,12 @@ $numColumns = sizeof($block->getColumns()); <button type="button" class="action-previous disabled"><span><?php /* @escapeNotVerified */ echo __('Previous page') ?></span></button> <?php endif; ?> <input type="text" - id="<?php echo $block->getHtmlId()?>_page-current" + id="<?php echo $block->escapeHTML($block->getHtmlId())?>_page-current" name="<?php /* @escapeNotVerified */ echo $block->getVarNamePage() ?>" value="<?php /* @escapeNotVerified */ echo $_curPage ?>" class="admin__control-text" onkeypress="<?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.inputPage(event, '<?php /* @escapeNotVerified */ echo $_lastPage ?>')" <?php /* @escapeNotVerified */ echo $block->getUiId('current-page') ?> /> - <label class="admin__control-support-text" for="<?php echo $block->getHtmlId()?>_page-current"> + <label class="admin__control-support-text" for="<?php echo $block->escapeHTML($block->getHtmlId())?>_page-current"> <?php /* @escapeNotVerified */ echo __('of %1', '<span>' . $block->getCollection()->getLastPageNumber() . '</span>') ?> </label> <?php if ($_curPage < $_lastPage): ?> @@ -133,7 +133,7 @@ $numColumns = sizeof($block->getColumns()); <?php endif; ?> <div class="admin__data-grid-wrap admin__data-grid-wrap-static"> - <table class="data-grid" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>_table"> + <table class="data-grid" id="<?php echo $block->escapeHtml($block->getId()) ?>_table"> <?php /* This part is commented to remove all <col> tags from the code. */ /* foreach ($block->getColumns() as $_column): ?> @@ -263,7 +263,7 @@ $numColumns = sizeof($block->getColumns()); registry.get('<?php /* @escapeNotVerified */ echo $block->getDependencyJsObject() ?>', function (<?php /* @escapeNotVerified */ echo $block->getDependencyJsObject() ?>) { <?php endif; ?> - <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?> = new varienGrid('<?php /* @escapeNotVerified */ echo $block->getId() ?>', '<?php /* @escapeNotVerified */ echo $block->getGridUrl() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNamePage() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameSort() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameDir() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameFilter() ?>'); + <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?> = new varienGrid(<?php /* @noEscape */ echo $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getId()) ?>, '<?php /* @escapeNotVerified */ echo $block->getGridUrl() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNamePage() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameSort() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameDir() ?>', '<?php /* @escapeNotVerified */ echo $block->getVarNameFilter() ?>'); <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.useAjax = '<?php /* @escapeNotVerified */ echo $block->getUseAjax() ?>'; <?php if ($block->getRowClickCallback()): ?> <?php /* @escapeNotVerified */ echo $block->getJsObjectName() ?>.rowClickCallback = <?php /* @escapeNotVerified */ echo $block->getRowClickCallback() ?>; diff --git a/app/code/Magento/Braintree/Block/Creditcard/Management.php b/app/code/Magento/Braintree/Block/Creditcard/Management.php index 079fe13183f418fae2c0ebc022a12cfabb7cb0d0..15a9c10dcd92864d6d4d8ddf4bd4e406f9b302f1 100644 --- a/app/code/Magento/Braintree/Block/Creditcard/Management.php +++ b/app/code/Magento/Braintree/Block/Creditcard/Management.php @@ -258,7 +258,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getEditUrl($token) { - return $this->getUrl('braintree/creditcard/edit', ['token' => $token]); + return $this->getUrl('braintree/creditcard/edit', ['token' => $token, '_secure' => true]); } /** @@ -269,7 +269,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getDeleteUrl($token) { - return $this->getUrl('braintree/creditcard/delete', ['token' => $token]); + return $this->getUrl('braintree/creditcard/delete', ['token' => $token, '_secure' => true]); } /** @@ -279,7 +279,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getAddUrl() { - return $this->getUrl('braintree/creditcard/newcard'); + return $this->getUrl('braintree/creditcard/newcard', ['_secure' => true]); } /** @@ -289,7 +289,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getDeleteConfirmUrl() { - return $this->getUrl('braintree/creditcard/deleteconfirm'); + return $this->getUrl('braintree/creditcard/deleteconfirm', ['_secure' => true]); } /** @@ -299,7 +299,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getAjaxSaveUrl() { - return $this->getUrl('braintree/creditcard/ajaxsave'); + return $this->getUrl('braintree/creditcard/ajaxsave', ['_secure' => true]); } /** @@ -309,7 +309,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getFormAction() { - return $this->getUrl('braintree/creditcard/save'); + return $this->getUrl('braintree/creditcard/save', ['_secure' => true]); } /** @@ -319,7 +319,7 @@ class Management extends \Magento\Framework\View\Element\Template */ public function getBackUrl() { - return $this->getUrl('braintree/creditcard/index'); + return $this->getUrl('braintree/creditcard/index', ['_secure' => true]); } /** diff --git a/app/code/Magento/Braintree/Block/PayPal/Shortcut.php b/app/code/Magento/Braintree/Block/PayPal/Shortcut.php index 6353447361150c3023b7449ac4944d97e50f90d6..dfb20982e85ae27bc82e91dfea065d6d6708baaa 100644 --- a/app/code/Magento/Braintree/Block/PayPal/Shortcut.php +++ b/app/code/Magento/Braintree/Block/PayPal/Shortcut.php @@ -125,7 +125,7 @@ class Shortcut extends \Magento\Framework\View\Element\Template implements Catal */ public function getReviewPageUrl() { - return $this->_urlBuilder->getUrl('braintree/paypal/review'); + return $this->_urlBuilder->getUrl('braintree/paypal/review', ['_secure' => true]); } /** diff --git a/app/code/Magento/Braintree/Controller/PayPal/SaveShippingMethod.php b/app/code/Magento/Braintree/Controller/PayPal/SaveShippingMethod.php index ed8746220bba2ee87adaa61a466ad8c897f5cdc7..d8041b7186ea4bb786a7827cd3e34595f6aaf5ba 100644 --- a/app/code/Magento/Braintree/Controller/PayPal/SaveShippingMethod.php +++ b/app/code/Magento/Braintree/Controller/PayPal/SaveShippingMethod.php @@ -38,11 +38,11 @@ class SaveShippingMethod extends \Magento\Braintree\Controller\PayPal if ($isAjax) { $this->getResponse()->setBody( '<script>window.location.href = ' - . $this->_url->getUrl('*/*/review') + . $this->_url->getUrl('*/*/review', ['_secure' => true]) . ';</script>' ); } else { - $this->_redirect('*/*/review'); + $this->_redirect('*/*/review', ['_secure' => true]); } } } diff --git a/app/code/Magento/Braintree/Model/ConfigProvider.php b/app/code/Magento/Braintree/Model/ConfigProvider.php index 70578f1ffae24794e6ac87434a69190695efd8a9..7bc41314ad84626666384b7e846b72b585029b51 100644 --- a/app/code/Magento/Braintree/Model/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/ConfigProvider.php @@ -134,7 +134,7 @@ class ConfigProvider extends CcGenericConfigProvider */ public function getAjaxGenerateNonceUrl() { - return $this->urlBuilder->getUrl('braintree/creditcard/generate'); + return $this->urlBuilder->getUrl('braintree/creditcard/generate', ['_secure' => true]); } /** diff --git a/app/code/Magento/Braintree/Model/PaymentMethod.php b/app/code/Magento/Braintree/Model/PaymentMethod.php index 0cf3f570ba2aa0d328b4c9d6311b3a78e6def126..e8c8627c5648755084836bd15f0c50333d907a44 100644 --- a/app/code/Magento/Braintree/Model/PaymentMethod.php +++ b/app/code/Magento/Braintree/Model/PaymentMethod.php @@ -95,7 +95,7 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc * @var bool */ protected $_canRefundInvoicePartial = true; - + /** * @var string */ @@ -289,7 +289,7 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc throw new LocalizedException($error); } } - + return $this; } @@ -474,7 +474,7 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc $this->_debug($transactionParams); try { $result = $this->braintreeTransaction->sale($transactionParams); - $this->_debug($result); + $this->_debug($this->_convertObjToArray($result)); } catch (\Exception $e) { $this->_logger->critical($e); throw new LocalizedException(__('Please try again later')); @@ -589,8 +589,8 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc $this->partialCapture($payment, $amount); } else { $result = $this->braintreeTransaction->submitForSettlement($payment->getCcTransId(), $amount); - $this->_debug($payment->getCcTransId().' - '.$amount); - $this->_debug($result); + $this->_debug([$payment->getCcTransId().' - '.$amount]); + $this->_debug($this->_convertObjToArray($result)); if ($result->success) { $payment->setIsTransactionClosed(0) ->setShouldCloseParentTransaction(false); @@ -621,8 +621,8 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc $transactionId = $this->braintreeHelper->clearTransactionId($payment->getRefundTransactionId()); try { $transaction = $this->braintreeTransaction->find($transactionId); - $this->_debug($payment->getCcTransId()); - $this->_debug($transaction); + $this->_debug([$payment->getCcTransId()]); + $this->_debug($this->_convertObjToArray($transaction)); if ($transaction->status === \Braintree_Transaction::SUBMITTED_FOR_SETTLEMENT) { if ($transaction->amount != $amount) { $message = __('This refund is for a partial amount but the Transaction has not settled.') @@ -641,7 +641,7 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc $result = $canVoid ? $this->braintreeTransaction->void($transactionId) : $this->braintreeTransaction->refund($transactionId, $amount); - $this->_debug($result); + $this->_debug($this->_convertObjToArray($result)); if ($result->success) { $payment->setIsTransactionClosed(1); } else { @@ -711,9 +711,9 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc } $errors = ''; foreach ($transactionIds as $transactionId) { - $this->_debug('void-' . $transactionId); + $this->_debug(['void-' . $transactionId]); $result = $this->braintreeTransaction->void($transactionId); - $this->_debug($result); + $this->_debug($this->_convertObjToArray($result)); if (!$result->success) { $errors .= ' ' . $this->errorHelper->parseBraintreeError($result)->getText(); } elseif ($message) { @@ -853,7 +853,7 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc */ protected function cloneTransaction($amount, $transactionId) { - $this->_debug('clone-' . $transactionId . ' amount=' . $amount); + $this->_debug(['clone-' . $transactionId . ' amount=' . $amount]); $result = $this->braintreeTransaction->cloneTransaction( $transactionId, [ @@ -863,7 +863,7 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc ] ] ); - $this->_debug($result); + $this->_debug($this->_convertObjToArray($result)); return $result; } @@ -907,28 +907,6 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc return $this->_canVoid; } - /** - * Log debug data to file - * - * @param mixed $debugData - * @return $this - */ - protected function _debug($debugData) - { - if (!$this->config->isDebugEnabled()) { - return $this; - } - if (!is_array($debugData)) { - if (is_object($debugData)) { - $debugData = var_export($debugData, true); - } else { - $debugData = [$debugData]; - } - } - parent::_debug((array)$debugData); - return $this; - } - /** * Return replace keys for debug data * @@ -957,4 +935,14 @@ class PaymentMethod extends \Magento\Payment\Model\Method\Cc } return $this->config->getConfigData($field, $storeId); } + + /** + * Convert response from Braintree to array + * @param \Braintree_Result_Successful|\Braintree_Result_Error|\Braintree_Transaction $data + * @return array + */ + protected function _convertObjToArray($data) + { + return json_decode(json_encode($data), true); + } } diff --git a/app/code/Magento/Braintree/Model/PaymentMethod/PayPal.php b/app/code/Magento/Braintree/Model/PaymentMethod/PayPal.php index ee868f41b03fd36df2697a509e82957da5a53ddd..3c89c87077a0832294c5b5b6dbdbe0e3bc890950 100644 --- a/app/code/Magento/Braintree/Model/PaymentMethod/PayPal.php +++ b/app/code/Magento/Braintree/Model/PaymentMethod/PayPal.php @@ -290,8 +290,8 @@ class PayPal extends \Magento\Braintree\Model\PaymentMethod try { if ($payment->getCcTransId()) { $result = $this->braintreeTransaction->submitForSettlement($payment->getCcTransId(), $amount); - $this->_debug($payment->getCcTransId().' - '.$amount); - $this->_debug($result); + $this->_debug([$payment->getCcTransId().' - '.$amount]); + $this->_debug($this->_convertObjToArray($result)); if ($result->success) { $payment->setIsTransactionClosed(0) ->setShouldCloseParentTransaction(false); diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index f762380db1ee2df5f97e7c0bc0ada4354dbf038f..ea679ab62015d36bb4e440209a359030eccf4ba0 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Config/etc/system_file.xsd"> <system> <section id="payment"> - <group id="braintree_section" translate="label" type="text" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="braintree_section" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Braintree</label> <comment><![CDATA[Accept credit/debit cards and PayPal in your Magento store. No setup or monthly fees and your customers never leave your store to complete the purchase.]]></comment> <attribute type="expanded">1</attribute> @@ -19,7 +19,7 @@ <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> <attribute type="activity_path">payment/braintree/active</attribute> <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Enabled Braintree</label> + <label>Enable this Solution</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/active</config_path> <requires> @@ -27,7 +27,7 @@ </requires> </field> <field id="active_braintree_pay_pal" translate="label" type="select" sortOrder="11" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Enabled PayPal through Braintree</label> + <label>Enable PayPal through Braintree</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/active</config_path> <requires> @@ -221,10 +221,10 @@ </field> </group> <group id="braintree_3dsecure" translate="label" showInDefault="1" showInWebsite="1" sortOrder="41"> - <label>3d Secure Verification Settings</label> + <label>3D Secure Verification Settings</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> <field id="verify_3dsecure" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>3d Secure Verification</label> + <label>3D Secure Verification</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/verify_3dsecure</config_path> </field> diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv index c4b98f7c7311c059012ad2d27227751493560e12..72919239a5585fbc786b688740cd0c68c3418271 100644 --- a/app/code/Magento/Braintree/i18n/en_US.csv +++ b/app/code/Magento/Braintree/i18n/en_US.csv @@ -282,3 +282,7 @@ "Invalid request format","Invalid request format" "Cannot update subscription to a plan with a different billing frequency.","Cannot update subscription to a plan with a different billing frequency." "Mismatch currency iso code","Mismatch currency iso code" +"Enable this Solution","Enable this Solution" +"Enable PayPal through Braintree","Enable PayPal through Braintree" +"3D Secure Verification Settings","3D Secure Verification Settings" +"3D Secure Verification","3D Secure Verification" diff --git a/app/code/Magento/Braintree/view/frontend/templates/PayPal/shortcut.phtml b/app/code/Magento/Braintree/view/frontend/templates/PayPal/shortcut.phtml index a2e80be40522cb1bebe99b7d0f7d978a97bb9e12..88b9a8397782dd282dea81cb933a0e8ed23730ea 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/PayPal/shortcut.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/PayPal/shortcut.phtml @@ -7,50 +7,36 @@ // @codingStandardsIgnoreFile /** @var \Magento\Braintree\Block\PayPal\Shortcut $block */ -$clientToken = $block->getClientToken(); -$amount = $block->getAmount(); -$url = $block->getReviewPageUrl(); -$currency = $block->getCurrency(); -$locale = $block->getLocale(); -$merchantName = $block->getMerchantName(); $containerId = $block->getContainerId(); $submitFormId = $block->getSubmitFormId(); -$enableBillingAddress = $block->enableBillingAddress(); $paymentMethodNonceId = $block->getPaymentMethodNonceId(); $paymentDetailsId = $block->getPaymentDetailsId(); -$labelPosition = $block->getShowOrPosition(); ?> <div data-label="<?php echo $block->escapeHtml(__('or')); ?>" - class="paypal checkout <?php /* @escapeNotVerified */ echo $labelPosition ?> paypal-logo" + class="paypal checkout <?php echo $block->escapeHtml($block->getShowOrPosition()) ?> paypal-logo" > <fieldset class="fieldset paypal items" id="braintree_paypal_shortcut"> - <div id="<?php /* @escapeNotVerified */ echo $containerId ?>"></div> - <form id="<?php /* @escapeNotVerified */ echo $submitFormId ?>" action="<?php /* @escapeNotVerified */ echo $url ?>" method="post" > - <input id="<?php /* @escapeNotVerified */ echo $paymentMethodNonceId ?>" type="hidden" name="payment_method_nonce" /> - <input id="<?php /* @escapeNotVerified */ echo $paymentDetailsId ?>" type="hidden" name="details" /> + <div + id="<?php /* @noEscape */ echo $containerId ?>" + data-mage-init='{ + "braintreePayPalShortcut":{ + "clientToken":"<?php echo $block->escapeHtml($block->getClientToken()) ?>", + "amount":"<?php /* @noEscape */ echo $block->getAmount() ?>", + "currency":"<?php /* @noEscape */ echo $block->getCurrency() ?>", + "locale":"<?php /* @noEscape */ echo $block->getLocale() ?>", + "merchantName":"<?php echo $block->escapeHtml($block->getMerchantName()) ?>", + "container":"<?php /* @noEscape */ echo $containerId ?>", + "submitFormId":"<?php /* @noEscape */ echo $submitFormId ?>", + "enableBillingAddress":"<?php /* @noEscape */ echo $block->enableBillingAddress() ?>", + "paymentMethodNonceId":"<?php /* @noEscape */ echo $paymentMethodNonceId ?>", + "paymentDetailsId":"<?php /* @noEscape */ echo $paymentDetailsId ?>" + } + }' + > + </div> + <form id="<?php /* @noEscape */ echo $submitFormId ?>" action="<?php echo $block->escapeUrl($block->getReviewPageUrl()) ?>" method="post" > + <input id="<?php /* @noEscape */ echo $paymentMethodNonceId ?>" type="hidden" name="payment_method_nonce" /> + <input id="<?php /* @noEscape */ echo $paymentDetailsId ?>" type="hidden" name="details" /> </form> </fieldset> -</div> - -<?php - $formData = [ - "clientToken" => $clientToken, - 'amount' => $amount, - 'currency' => $currency, - 'locale' => $locale, - 'merchantName' => $merchantName, - 'container' => $containerId, - 'submitFormId' => $submitFormId, - 'enableBillingAddress' => $enableBillingAddress, - 'paymentMethodNonceId' => $paymentMethodNonceId, - 'paymentDetailsId' => $paymentDetailsId - ]; - $serializedFormData = $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($formData); -?> -<script type="text/x-magento-init"> - { - "#<?php /* @escapeNotVerified */ echo $containerId ?>": { - "braintreePayPalShortcut": <?php /* @escapeNotVerified */ echo $serializedFormData ?> - } - } -</script> +</div> \ No newline at end of file diff --git a/app/code/Magento/Braintree/view/frontend/web/js/braintree-paypal-shortcut.js b/app/code/Magento/Braintree/view/frontend/web/js/braintree-paypal-shortcut.js index 0a41c60ca40ad4212c1ce71312221a5ada6bb7a7..8880c65fd87bc3dea825691c9578efb337a6e604 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/braintree-paypal-shortcut.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/braintree-paypal-shortcut.js @@ -29,7 +29,17 @@ define([ var paymentDetailsId = this.options.paymentDetailsId; var submitFormId = this.options.submitFormId; var self = this; - $('#'.concat(this.options.container)).empty(); + var $container = $('#'.concat(this.options.container)); + $container.empty(); + + // braintree setup should be called only once, + // but widget._create() called twice (related to minicart observable attributes implementation) + if ($container.data('rendered')) { + return; + } + $container.data('rendered', true); + + braintree.setup(clientToken, "paypal", { container: this.options.container, singleUse: true, diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js index 57ea5d8e6f5ef5b8815896bb1e8e2aa23a88d7be..ba203615d426260d4a37dc32b94c90dd3e9594c8 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js @@ -51,8 +51,8 @@ define( selectedCardToken: configBraintree ? configBraintree.selectedCardToken : '', storedCards: configBraintree ? configBraintree.storedCards : {}, availableCardTypes: configBraintree ? configBraintree.availableCardTypes : {}, - creditCardExpMonth: configBraintree ? configBraintree.creditCardExpMonth : null, - creditCardExpYear: configBraintree ? configBraintree.creditCardExpYear : null + creditCardExpMonth: null, + creditCardExpYear: null }, initVars: function() { this.ajaxGenerateNonceUrl = configBraintree ? configBraintree.ajaxGenerateNonceUrl : ''; @@ -349,6 +349,10 @@ define( } }); } + }, + + getCssClass: function () { + return (this.isCcDetectionEnabled()) ? 'field type detection' : 'field type required'; } }); } diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/cc-form.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/cc-form.html index d7ef8969eeae1cf92895e58d6d6d2c6ac41c17fc..f28cb33ceb4b1e1b13c85cd7d537fcf95377a434 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/cc-form.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/cc-form.html @@ -1,6 +1,3 @@ - - - <!-- /** * Copyright © 2015 Magento. All rights reserved. @@ -61,10 +58,12 @@ </div> <div> </div> <!-- /ko --> - <div class="field type required" data-bind="visible: isCcFormShown"> + <div data-bind="visible: isCcFormShown, attr: {class: getCssClass()}"> + <!-- ko if: (!isCcDetectionEnabled())--> <label data-bind="attr: {for: getCode() + '_cc_type'}" class="label"> <span><!-- ko i18n: 'Credit Card Type'--><!-- /ko --></span> </label> + <!-- /ko --> <div class="control"> <!-- ko if: (!isCcDetectionEnabled())--> <select name="payment[cc_type]" class="select" @@ -153,7 +152,7 @@ <label data-bind="attr: {for: getCode() + '_cc_cid'}" class="label"> <span><!-- ko i18n: 'Card Verification Number'--><!-- /ko --></span> </label> - <div class="control"> + <div class="control _with-tooltip"> <input type="number" class="input-text cvv" name="payment[cc_cid]" diff --git a/app/code/Magento/Bundle/Api/Data/BundleOptionInterface.php b/app/code/Magento/Bundle/Api/Data/BundleOptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..f489845b55cbf925fa69c10a1913ddc988f39e64 --- /dev/null +++ b/app/code/Magento/Bundle/Api/Data/BundleOptionInterface.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Api\Data; + +/** + * Interface BundleOptionInterface + * @api + */ +interface BundleOptionInterface extends \Magento\Framework\Api\ExtensibleDataInterface +{ + /** + * Get bundle option id. + * + * @return int + */ + public function getOptionId(); + + /** + * Set bundle option id. + * + * @param int $optionId + * @return int + */ + public function setOptionId($optionId); + + /** + * Get bundle option quantity. + * + * @return int + */ + public function getOptionQty(); + + /** + * Set bundle option quantity. + * + * @param int $optionQty + * @return int + */ + public function setOptionQty($optionQty); + + /** + * Get bundle option selection ids. + * + * @return int[] + */ + public function getOptionSelections(); + + /** + * Set bundle option selection ids. + * + * @param int[] $optionSelections + * @return int[] + */ + public function setOptionSelections(array $optionSelections); + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Bundle\Api\Data\BundleOptionExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\Bundle\Api\Data\BundleOptionExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Bundle\Api\Data\BundleOptionExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes.php b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes.php index 06367ff6da556605ad8bb04f9c941499513b17b0..3786a2605e588c57d3342cbc55115e5569caf162 100644 --- a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes.php +++ b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Attributes.php @@ -126,19 +126,6 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Attri ) ); } - - $groupPrice = $this->getForm()->getElement('group_price'); - if ($groupPrice) { - $groupPrice->setRenderer( - $this->getLayout()->createBlock( - 'Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price\Group' - )->setPriceColumnHeader( - __('Percent Discount') - )->setPriceValidation( - 'validate-greater-than-zero validate-number-range number-range-0.00-100.00' - ) - ); - } } /** diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php index 1f97b56488bcdfd5dff4742ca9905eaafe297316..8ab078f1b3ec0d825d42e2eccbab7a62da82dea7 100644 --- a/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php +++ b/app/code/Magento/Bundle/Block/Adminhtml/Catalog/Product/Edit/Tab/Bundle.php @@ -66,6 +66,7 @@ class Bundle extends \Magento\Backend\Block\Widget implements \Magento\Backend\B */ protected function _prepareLayout() { + $this->setData('opened', true); $this->addChild( 'add_button', 'Magento\Backend\Block\Widget\Button', diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/SuggestProductTemplates.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/SuggestProductTemplates.php deleted file mode 100644 index 1fdb714136b6d0902eb5df2da1e63e04aa2d6700..0000000000000000000000000000000000000000 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Product/Edit/SuggestProductTemplates.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit; - -class SuggestProductTemplates extends \Magento\Catalog\Controller\Adminhtml\Product\SuggestProductTemplates -{ -} diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Selection/Grid.php b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Selection/Grid.php index 85ce181f5eb1957de3c5c205593d496931caf82e..684eb43e4ad6019b28243f9ef932f74d4e7f16e9 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Selection/Grid.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Bundle/Selection/Grid.php @@ -13,13 +13,16 @@ class Grid extends \Magento\Backend\App\Action */ public function execute() { + $index = $this->getRequest()->getParam('index'); + if (!preg_match('/^[a-z0-9_.]*$/i', $index)) { + throw new \InvalidArgumentException('Invalid parameter "index"'); + } + return $this->getResponse()->setBody( $this->_view->getLayout()->createBlock( 'Magento\Bundle\Block\Adminhtml\Catalog\Product\Edit\Tab\Bundle\Option\Search\Grid', 'adminhtml.catalog.product.edit.tab.bundle.option.search.grid' - )->setIndex( - $this->getRequest()->getParam('index') - )->toHtml() + )->setIndex($index)->toHtml() ); } } diff --git a/app/code/Magento/Bundle/Model/BundleOption.php b/app/code/Magento/Bundle/Model/BundleOption.php new file mode 100644 index 0000000000000000000000000000000000000000..f44694c2c66d08d032ff190718e125b7ed8737f9 --- /dev/null +++ b/app/code/Magento/Bundle/Model/BundleOption.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Bundle\Model; + +use Magento\Framework\Model\AbstractExtensibleModel; +use Magento\Bundle\Api\Data\BundleOptionInterface; + +class BundleOption extends AbstractExtensibleModel implements BundleOptionInterface +{ + /**#@+ + * Constants + */ + const OPTION_ID = 'option_id'; + const OPTION_QTY = 'option_qty'; + const OPTION_SELECTIONS = 'option_selections'; + /**#@-*/ + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function getOptionId() + { + return $this->getData(self::OPTION_ID); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function getOptionQty() + { + return $this->getData(self::OPTION_QTY); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function getOptionSelections() + { + return $this->getData(self::OPTION_SELECTIONS); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function setOptionId($optionId) + { + return $this->setData(self::OPTION_ID, $optionId); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function setOptionQty($optionQty) + { + return $this->setData(self::OPTION_QTY, $optionQty); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function setOptionSelections(array $optionSelections) + { + return $this->setData(self::OPTION_SELECTIONS, $optionSelections); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function setExtensionAttributes(\Magento\Bundle\Api\Data\BundleOptionExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Bundle/Model/CartItemProcessor.php b/app/code/Magento/Bundle/Model/CartItemProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..28fabb39a9ed176d145c01edccea5a5093a76805 --- /dev/null +++ b/app/code/Magento/Bundle/Model/CartItemProcessor.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Bundle\Model; + +use Magento\Quote\Model\Quote\Item\CartItemProcessorInterface; +use Magento\Quote\Api\Data\CartItemInterface; +use Magento\Bundle\Api\Data\BundleOptionInterfaceFactory; +use Magento\Quote\Api\Data as QuoteApi; + +class CartItemProcessor implements CartItemProcessorInterface +{ + /** + * @var \Magento\Framework\DataObject\Factory + */ + protected $objectFactory; + + /** + * @var QuoteApi\ProductOptionExtensionFactory + */ + protected $productOptionExtensionFactory; + + /** + * @var BundleOptionInterfaceFactory + */ + protected $bundleOptionFactory; + + /** + * @var QuoteApi\ProductOptionInterfaceFactory + */ + protected $productOptionFactory; + + /** + * @param \Magento\Framework\DataObject\Factory $objectFactory + * @param QuoteApi\ProductOptionExtensionFactory $productOptionExtensionFactory + * @param BundleOptionInterfaceFactory $bundleOptionFactory + * @param QuoteApi\ProductOptionInterfaceFactory $productOptionFactory + */ + public function __construct( + \Magento\Framework\DataObject\Factory $objectFactory, + QuoteApi\ProductOptionExtensionFactory $productOptionExtensionFactory, + BundleOptionInterfaceFactory $bundleOptionFactory, + QuoteApi\ProductOptionInterfaceFactory $productOptionFactory + ) { + $this->objectFactory = $objectFactory; + $this->productOptionExtensionFactory = $productOptionExtensionFactory; + $this->bundleOptionFactory = $bundleOptionFactory; + $this->productOptionFactory = $productOptionFactory; + } + + /** + * {@inheritdoc} + */ + public function convertToBuyRequest(CartItemInterface $cartItem) + { + if ($cartItem->getProductOption() && $cartItem->getProductOption()->getExtensionAttributes()) { + $options = $cartItem->getProductOption()->getExtensionAttributes()->getBundleOptions(); + if (is_array($options)) { + $requestData = []; + foreach ($options as $option) { + /** @var \Magento\Bundle\Api\Data\BundleOptionInterface $option */ + foreach ($option->getOptionSelections() as $selection) { + $requestData['bundle_option'][$option->getOptionId()][] = $selection; + $requestData['bundle_option_qty'][$option->getOptionId()] = $option->getOptionQty(); + } + } + return $this->objectFactory->create($requestData); + } + } + return null; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function processProductOptions(CartItemInterface $cartItem) + { + if ($cartItem->getProductType() !== \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) { + return $cartItem; + } + $productOptions = []; + $bundleOptions = $cartItem->getBuyRequest()->getBundleOption(); + $bundleOptionsQty = $cartItem->getBuyRequest()->getBundleOptionQty(); + foreach ($bundleOptions as $optionId => $optionSelections) { + if (empty($optionSelections)) { + continue; + } + $optionSelections = is_array($optionSelections) ? $optionSelections : [$optionSelections]; + $optionQty = isset($bundleOptionsQty[$optionId]) ? $bundleOptionsQty[$optionId] : 1; + + /** @var \Magento\Bundle\Api\Data\BundleOptionInterface $productOption */ + $productOption = $this->bundleOptionFactory->create(); + $productOption->setOptionId($optionId); + $productOption->setOptionSelections($optionSelections); + $productOption->setOptionQty($optionQty); + $productOptions[] = $productOption; + } + + $extension = $this->productOptionExtensionFactory->create()->setBundleOptions($productOptions); + if (!$cartItem->getProductOption()) { + $cartItem->setProductOption($this->productOptionFactory->create()); + } + $cartItem->getProductOption()->setExtensionAttributes($extension); + return $cartItem; + } +} diff --git a/app/code/Magento/Bundle/Model/Product/Price.php b/app/code/Magento/Bundle/Model/Product/Price.php index 9c0163fcc4cb9081e24c487ecb055ade1900d3c0..a2c0430d00d431cea0aaa587064d8ff07a4c7998 100644 --- a/app/code/Magento/Bundle/Model/Product/Price.php +++ b/app/code/Magento/Bundle/Model/Product/Price.php @@ -40,6 +40,8 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price protected $_catalogData = null; /** + * Price constructor. + * * @param \Magento\CatalogRule\Model\Resource\RuleFactory $ruleFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate @@ -47,7 +49,6 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param PriceCurrencyInterface $priceCurrency * @param GroupManagementInterface $groupManagement - * @param \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory $groupPriceFactory * @param \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @param \Magento\Catalog\Helper\Data $catalogData @@ -62,7 +63,6 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price \Magento\Framework\Event\ManagerInterface $eventManager, PriceCurrencyInterface $priceCurrency, GroupManagementInterface $groupManagement, - \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory $groupPriceFactory, \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory, \Magento\Framework\App\Config\ScopeConfigInterface $config, \Magento\Catalog\Helper\Data $catalogData @@ -76,7 +76,6 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price $eventManager, $priceCurrency, $groupManagement, - $groupPriceFactory, $tierPriceFactory, $config ); @@ -98,6 +97,7 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price * * @param \Magento\Catalog\Model\Product $product * @return float + * * @deprecated (MAGETWO-31476) */ public function getPrice($product) @@ -447,68 +447,11 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price return min( $price, - $this->_applyGroupPrice($bundleProduct, $price), $this->_applyTierPrice($bundleProduct, $bundleQty, $price), $this->_applySpecialPrice($bundleProduct, $price) ); } - /** - * Apply group price for bundle product - * - * @param \Magento\Catalog\Model\Product $product - * @param float $finalPrice - * @return float - */ - protected function _applyGroupPrice($product, $finalPrice) - { - $result = $finalPrice; - $groupPrice = $product->getGroupPrice(); - - if (is_numeric($groupPrice)) { - $groupPrice = $finalPrice - $finalPrice * ($groupPrice / 100); - $result = min($finalPrice, $groupPrice); - } - - return $result; - } - - /** - * Get product group price - * - * @param \Magento\Catalog\Model\Product $product - * @return float|null - */ - public function getGroupPrice($product) - { - $groupPrices = $product->getData('group_price'); - - if ($groupPrices === null) { - $attribute = $product->getResource()->getAttribute('group_price'); - if ($attribute) { - $attribute->getBackend()->afterLoad($product); - $groupPrices = $product->getData('group_price'); - } - } - - if ($groupPrices === null || !is_array($groupPrices)) { - return null; - } - - $customerGroup = $this->_getCustomerGroupId($product); - - $matchedPrice = 0; - - foreach ($groupPrices as $groupPrice) { - if ($groupPrice['cust_group'] == $customerGroup && $groupPrice['website_price'] > $matchedPrice) { - $matchedPrice = $groupPrice['website_price']; - break; - } - } - - return $matchedPrice; - } - /** * Apply tier price for bundle * @@ -625,16 +568,6 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price return $prices ? $prices : []; } - /** - * Check is group price value fixed or percent of original price - * - * @return bool - */ - public function isGroupPriceFixed() - { - return false; - } - /** * Calculate and apply special price * @@ -675,7 +608,6 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price $price = (float)$price; return min( $price, - $this->_applyGroupPrice($bundleProduct, $price), $this->_applyTierPrice($bundleProduct, $bundleQty, $price), $this->_applySpecialPrice($bundleProduct, $price) ); diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index 9217fd82ab9a804761fcb70ef5c37c814e0dbd36..a22f16616a88687282e694665bc720630804d832 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -883,6 +883,12 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType ->addFilterByRequiredOptions() ->setSelectionIdsFilter($selectionIds); + if (count($usedSelections->getItems()) !== count($selectionIds)) { + throw new \Magento\Framework\Exception\LocalizedException( + __('The options you selected are not available.') + ); + } + if (!$this->_catalogData->isPriceGlobal() && $storeId) { $websiteId = $this->_storeManager->getStore($storeId) ->getWebsiteId(); diff --git a/app/code/Magento/Bundle/Model/Resource/Indexer/Price.php b/app/code/Magento/Bundle/Model/Resource/Indexer/Price.php index 651fd339fcc274ddcf130b0aca74c094c7ebfad2..a766f993cddcc295ef46539c645dbd3b40e53eff 100644 --- a/app/code/Magento/Bundle/Model/Resource/Indexer/Price.php +++ b/app/code/Magento/Bundle/Model/Resource/Indexer/Price.php @@ -145,11 +145,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' . ' AND tp.customer_group_id = cg.customer_group_id', [] - )->joinLeft( - ['gp' => $this->_getGroupPriceIndexTable()], - 'gp.entity_id = e.entity_id AND gp.website_id = cw.website_id' . - ' AND gp.customer_group_id = cg.customer_group_id', - [] )->where( 'e.type_id=?', $this->getTypeId() @@ -198,12 +193,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul '0' ); - $groupPriceExpr = $connection->getCheckSql( - 'gp.price IS NOT NULL AND gp.price > 0 AND gp.price < 100', - 'gp.price', - '0' - ); - $tierExpr = new \Zend_Db_Expr("tp.min_price"); if ($priceType == \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED) { @@ -217,20 +206,15 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'ROUND(' . $price . ' - ' . '(' . $price . ' * (' . $tierExpr . ' / 100)), 4)', 'NULL' ); - $groupPrice = $connection->getCheckSql( - $groupPriceExpr . ' > 0', - 'ROUND(' . $price . ' - ' . '(' . $price . ' * (' . $groupPriceExpr . ' / 100)), 4)', - 'NULL' - ); + $finalPrice = $connection->getCheckSql( - "{$groupPrice} IS NOT NULL AND {$groupPrice} < {$finalPrice}", - $groupPrice, + "{$tierPrice} < {$finalPrice}", + $tierPrice, $finalPrice ); } else { $finalPrice = new \Zend_Db_Expr("0"); $tierPrice = $connection->getCheckSql($tierExpr . ' IS NOT NULL', '0', 'NULL'); - $groupPrice = $connection->getCheckSql($groupPriceExpr . ' > 0', $groupPriceExpr, 'NULL'); } $select->columns( @@ -244,9 +228,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'max_price' => $finalPrice, 'tier_price' => $tierPrice, 'base_tier' => $tierPrice, - 'group_price' => $groupPrice, - 'base_group_price' => $groupPrice, - 'group_price_percent' => new \Zend_Db_Expr('gp.price'), ] ); @@ -300,8 +281,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'max_price' => $connection->getCheckSql('i.group_type = 1', 'SUM(i.price)', 'MAX(i.price)'), 'tier_price' => $connection->getCheckSql('i.is_required = 1', 'MIN(i.tier_price)', '0'), 'alt_tier_price' => $connection->getCheckSql('i.is_required = 0', 'MIN(i.tier_price)', '0'), - 'group_price' => $connection->getCheckSql('i.is_required = 1', 'MIN(i.group_price)', '0'), - 'alt_group_price' => $connection->getCheckSql('i.is_required = 0', 'MIN(i.group_price)', '0'), ] ); @@ -323,15 +302,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul ) . ' + MIN(i.tier_price)', 'NULL' ); - $groupPrice = $connection->getCheckSql( - 'MIN(i.group_price_percent) IS NOT NULL', - $connection->getCheckSql( - 'SUM(io.group_price) = 0', - 'SUM(io.alt_group_price)', - 'SUM(io.group_price)' - ) . ' + MIN(i.group_price)', - 'NULL' - ); $select = $connection->select()->from( ['io' => $this->_getBundleOptionTable()], @@ -352,8 +322,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'max_price' => $maxPrice, 'tier_price' => $tierPrice, 'base_tier' => 'MIN(i.base_tier)', - 'group_price' => $groupPrice, - 'base_group_price' => 'MIN(i.base_group_price)', ] ); @@ -415,25 +383,8 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'NULL' ); - $groupExpr = $connection->getCheckSql( - 'i.base_group_price IS NOT NULL', - $connection->getCheckSql( - $selectionPriceType . ' = 1', - $priceExpr, - $connection->getCheckSql( - 'i.group_price_percent > 0', - 'ROUND(' . - $selectionPriceValue . - ' - (' . - $selectionPriceValue . - ' * (i.group_price_percent / 100)),4)', - $selectionPriceValue - ) - ) . ' * bs.selection_qty', - 'NULL' - ); $priceExpr = new \Zend_Db_Expr( - $connection->getCheckSql("{$groupExpr} < {$priceExpr}", $groupExpr, $priceExpr) + $connection->getCheckSql("{$tierExpr} < {$priceExpr}", $tierExpr, $priceExpr) ); } else { $priceExpr = new \Zend_Db_Expr( @@ -448,21 +399,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'ROUND(idx.min_price * (i.base_tier / 100), 4)* bs.selection_qty', 'NULL' ); - $groupExpr = $connection->getCheckSql( - 'i.base_group_price IS NOT NULL', - 'ROUND(idx.min_price * (i.base_group_price / 100), 4)* bs.selection_qty', - 'NULL' - ); - $groupPriceExpr = new \Zend_Db_Expr( - $connection->getCheckSql( - 'i.base_group_price IS NOT NULL AND i.base_group_price > 0 AND i.base_group_price < 100', - 'ROUND(idx.min_price - idx.min_price * (i.base_group_price / 100), 4)', - 'idx.min_price' - ) . ' * bs.selection_qty' - ); - $priceExpr = new \Zend_Db_Expr( - $connection->getCheckSql("{$groupPriceExpr} < {$priceExpr}", $groupPriceExpr, $priceExpr) - ); } $select = $connection->select()->from( @@ -498,7 +434,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'is_required' => 'bo.required', 'price' => $priceExpr, 'tier_price' => $tierExpr, - 'group_price' => $groupExpr, ] ); @@ -520,7 +455,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul return $this; } $this->_prepareTierPriceIndex($entityIds); - $this->_prepareGroupPriceIndex($entityIds); $this->_prepareBundlePriceTable(); $this->_prepareBundlePriceByType(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, $entityIds); $this->_prepareBundlePriceByType(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC, $entityIds); @@ -593,65 +527,4 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul return $this; } - - /** - * Prepare percentage group price for bundle products - * - * @param int|array $entityIds - * @return $this - */ - protected function _prepareGroupPriceIndex($entityIds = null) - { - $connection = $this->getConnection(); - - // remove index by bundle products - $select = $connection->select()->from( - ['i' => $this->_getGroupPriceIndexTable()], - null - )->join( - ['e' => $this->getTable('catalog_product_entity')], - 'i.entity_id=e.entity_id', - [] - )->where( - 'e.type_id=?', - $this->getTypeId() - ); - $query = $select->deleteFromSelect('i'); - $connection->query($query); - - $select = $connection->select()->from( - ['gp' => $this->getTable('catalog_product_entity_group_price')], - ['entity_id'] - )->join( - ['e' => $this->getTable('catalog_product_entity')], - 'gp.entity_id=e.entity_id', - [] - )->join( - ['cg' => $this->getTable('customer_group')], - 'gp.all_groups = 1 OR (gp.all_groups = 0 AND gp.customer_group_id = cg.customer_group_id)', - ['customer_group_id'] - )->join( - ['cw' => $this->getTable('store_website')], - 'gp.website_id = 0 OR gp.website_id = cw.website_id', - ['website_id'] - )->where( - 'cw.website_id != 0' - )->where( - 'e.type_id=?', - $this->getTypeId() - )->columns( - new \Zend_Db_Expr('MIN(gp.value)') - )->group( - ['gp.entity_id', 'cg.customer_group_id', 'cw.website_id'] - ); - - if (!empty($entityIds)) { - $select->where('gp.entity_id IN(?)', $entityIds); - } - - $query = $select->insertFromSelect($this->_getGroupPriceIndexTable()); - $connection->query($query); - - return $this; - } } diff --git a/app/code/Magento/Bundle/Pricing/Price/GroupPrice.php b/app/code/Magento/Bundle/Pricing/Price/GroupPrice.php deleted file mode 100644 index 3989c5c17913895794b64ee9bb38b2ece5ef8737..0000000000000000000000000000000000000000 --- a/app/code/Magento/Bundle/Pricing/Price/GroupPrice.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Bundle\Pricing\Price; - -use Magento\Catalog\Pricing\Price\RegularPrice; - -/** - * Bundle group price model - */ -class GroupPrice extends \Magento\Catalog\Pricing\Price\GroupPrice implements DiscountProviderInterface -{ - /** - * @var float|false - */ - protected $percent; - - /** - * Returns percent discount value - * - * @return bool|float - */ - public function getDiscountPercent() - { - if ($this->percent === null) { - $percent = parent::getValue(); - $this->percent = ($percent) ? max(0, min(100, 100 - $percent)) : null; - } - return $this->percent; - } - - /** - * Returns pice value - * - * @return float|bool - */ - public function getValue() - { - if ($this->value !== null) { - return $this->value; - } - - $groupPrice = $this->getDiscountPercent(); - if ($groupPrice) { - $regularPrice = $this->getRegularPrice(); - $this->value = $regularPrice * ($groupPrice / 100); - } else { - $this->value = false; - } - return $this->value; - } - - /** - * Returns regular price - * - * @return bool|float - */ - protected function getRegularPrice() - { - return $this->priceInfo->getPrice(RegularPrice::PRICE_CODE)->getValue(); - } - - /** - * @return bool - */ - public function isPercentageDiscount() - { - return true; - } -} diff --git a/app/code/Magento/Bundle/Setup/InstallData.php b/app/code/Magento/Bundle/Setup/InstallData.php index b24e8b68dbc715a5fb493ad7b998f3dee925508e..16a1409304e42c20cd2ea892c30ff24295f5782e 100644 --- a/app/code/Magento/Bundle/Setup/InstallData.php +++ b/app/code/Magento/Bundle/Setup/InstallData.php @@ -51,7 +51,6 @@ class InstallData implements InstallDataInterface 'cost', 'tier_price', 'weight', - 'group_price', ]; foreach ($fieldList as $field) { $applyTo = explode( diff --git a/app/code/Magento/Bundle/Setup/InstallSchema.php b/app/code/Magento/Bundle/Setup/InstallSchema.php index 94325c54ff13c965a23e20a7b1962818b5816d2f..74263a04f76df6143b6c045a1dc513e4361d50e9 100644 --- a/app/code/Magento/Bundle/Setup/InstallSchema.php +++ b/app/code/Magento/Bundle/Setup/InstallSchema.php @@ -549,27 +549,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Base Tier' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) - ->addColumn( - 'base_group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Base Group Price' - ) - ->addColumn( - 'group_price_percent', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group Price Percent' - ) ->setComment('Catalog Product Index Price Bundle Idx'); $installer->getConnection()->createTable($table); @@ -670,27 +649,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Base Tier' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) - ->addColumn( - 'base_group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Base Group Price' - ) - ->addColumn( - 'group_price_percent', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group Price Percent' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -767,13 +725,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setComment('Catalog Product Index Price Bundle Sel Idx'); $installer->getConnection()->createTable($table); @@ -846,13 +797,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -929,20 +873,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Alt Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) - ->addColumn( - 'alt_group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Alt Group Price' - ) ->setComment('Catalog Product Index Price Bundle Opt Idx'); $installer->getConnection()->createTable($table); @@ -1015,20 +945,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Alt Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) - ->addColumn( - 'alt_group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Alt Group Price' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY diff --git a/app/code/Magento/Bundle/Setup/UpgradeSchema.php b/app/code/Magento/Bundle/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..3f9caf16e3d6f312a399ee252273a232179d4f57 --- /dev/null +++ b/app/code/Magento/Bundle/Setup/UpgradeSchema.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Setup; + +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * @codeCoverageIgnore + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + $connection = $setup->getConnection(); + if (version_compare($context->getVersion(), '2.0.1', '<')) { + + $fields = [ + ['table' => 'catalog_product_index_price_bundle_opt_idx', 'column' => 'alt_group_price'], + ['table' => 'catalog_product_index_price_bundle_opt_tmp', 'column' => 'alt_group_price'], + ['table' => 'catalog_product_index_price_bundle_idx', 'column' => 'base_group_price'], + ['table' => 'catalog_product_index_price_bundle_tmp', 'column' => 'base_group_price'], + ['table' => 'catalog_product_index_price_bundle_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_bundle_opt_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_bundle_opt_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_bundle_sel_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_bundle_sel_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_bundle_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_bundle_idx', 'column' => 'group_price_percent'], + ['table' => 'catalog_product_index_price_bundle_tmp', 'column' => 'group_price_percent'], + ]; + + foreach ($fields as $filedInfo) { + $connection->dropColumn($setup->getTable($filedInfo['table']), $filedInfo['column']); + } + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Bundle/Selection/GridTest.php b/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Bundle/Selection/GridTest.php index 267dade65be131b972fdaa5e9254603cc8ef9bc6..c0a7342f8249fe51452f5b762116455f814a9581 100644 --- a/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Bundle/Selection/GridTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Bundle/Selection/GridTest.php @@ -90,4 +90,15 @@ class GridTest extends \PHPUnit_Framework_TestCase $this->assertEquals($this->response, $this->controller->execute()); } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid parameter "index" + */ + public function testExecuteWithException() + { + $this->request->expects($this->once())->method('getParam')->with('index')->willReturn('<index"'); + + $this->controller->execute(); + } } diff --git a/app/code/Magento/Bundle/Test/Unit/Model/CartItemProcessorTest.php b/app/code/Magento/Bundle/Test/Unit/Model/CartItemProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2ffde6aa43b640d6c1ddbde28b4b1f5185004284 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Unit/Model/CartItemProcessorTest.php @@ -0,0 +1,159 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Bundle\Test\Unit\Model; + +use Magento\Catalog\Model\Product\Type; + +class CartItemProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $objectFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $productOptionExtensionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $bundleOptionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $productOptionFactoryMock; + + /** + * @var \Magento\Bundle\Model\CartItemProcessor + */ + protected $model; + + protected function setUp() + { + $this->objectFactoryMock = $this->getMock('\Magento\Framework\DataObject\Factory', ['create'], [], '', false); + $this->productOptionExtensionMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtensionFactory', + ['create'], + [], + '', + false + ); + $this->bundleOptionFactoryMock = $this->getMock( + '\Magento\Bundle\Api\Data\BundleOptionInterfaceFactory', + ['create'], + [], + '', + false + ); + $this->productOptionFactoryMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionInterfaceFactory', + ['create'], + [], + '', + false + ); + + $this->model = new \Magento\Bundle\Model\CartItemProcessor( + $this->objectFactoryMock, + $this->productOptionExtensionMock, + $this->bundleOptionFactoryMock, + $this->productOptionFactoryMock + ); + } + + public function testConvertToBuyRequest() + { + $optionSelections = [42]; + $optionQty = 1; + $optionId = 4; + + $bundleOptionMock = $this->getMock('\Magento\Bundle\Model\BundleOption', [], [], '', false); + $cartItemMock = $this->getMock('\Magento\Quote\Model\Quote\Item', [], [], '', false); + $productOptionMock = $this->getMock('\Magento\Quote\Model\Quote\ProductOption', [], [], '', false); + $dataObjectMock = $this->getMock('\Magento\Framework\DataObject'); + $optionExtensionMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtensionInterface', + ['getBundleOptions'], + [], + '', + false + ); + $requestDataMock = [ + 'bundle_option' => [$optionId => $optionSelections], + 'bundle_option_qty' => [$optionId => $optionQty] + ]; + + $cartItemMock->expects($this->atLeastOnce())->method('getProductOption')->willReturn($productOptionMock); + $productOptionMock->expects($this->atLeastOnce())->method('getExtensionAttributes') + ->willReturn($optionExtensionMock); + $optionExtensionMock->expects($this->atLeastOnce())->method('getBundleOptions') + ->willReturn([$bundleOptionMock]); + $bundleOptionMock->expects($this->once())->method('getOptionSelections')->willReturn($optionSelections); + $bundleOptionMock->expects($this->once())->method('getOptionQty')->willReturn($optionQty); + $bundleOptionMock->expects($this->atLeastOnce())->method('getOptionId')->willReturn($optionId); + $this->objectFactoryMock->expects($this->once())->method('create')->with($requestDataMock) + ->willReturn($dataObjectMock); + + $this->assertEquals($dataObjectMock, $this->model->convertToBuyRequest($cartItemMock)); + } + + public function testConvertToBuyRequestInvalidData() + { + $cartItemMock = $this->getMock('\Magento\Quote\Model\Quote\Item', [], [], '', false); + $this->assertNull($this->model->convertToBuyRequest($cartItemMock)); + } + + public function testProcessProductOptions() + { + $optionId = 4; + $optionSelections = 42; + $optionQty = 1; + $bundleOption = [$optionId => $optionSelections, 5 => ""]; + $bundleOptionQty = [$optionId => $optionQty]; + + $buyRequestMock = new \Magento\Framework\DataObject( + [ + 'bundle_option' => $bundleOption, + 'bundle_option_qty' => $bundleOptionQty + ] + ); + $cartItemMock = $this->getMock('\Magento\Quote\Model\Quote\Item', [], [], '', false); + $bundleOptionMock = $this->getMock('\Magento\Bundle\Model\BundleOption', [], [], '', false); + $productOptionMock = $this->getMock('\Magento\Quote\Model\Quote\ProductOption', [], [], '', false); + $optionExtensionMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtensionInterface', + ['setBundleOptions'], + [], + '', + false + ); + + $cartItemMock->expects($this->once())->method('getProductType')->willReturn(Type::TYPE_BUNDLE); + $cartItemMock->expects($this->atLeastOnce())->method('getBuyRequest')->willReturn($buyRequestMock); + $this->bundleOptionFactoryMock->expects($this->once())->method('create')->willReturn($bundleOptionMock); + $bundleOptionMock->expects($this->once())->method('setOptionId')->with($optionId)->willReturnSelf(); + $bundleOptionMock->expects($this->once())->method('setOptionSelections')->with([$optionSelections]) + ->willReturnSelf(); + $bundleOptionMock->expects($this->once())->method('setOptionQty')->with($optionQty)->willReturnSelf(); + $this->productOptionExtensionMock->expects($this->once())->method('create')->willReturn($optionExtensionMock); + $optionExtensionMock->expects($this->once())->method('setBundleOptions')->with([$bundleOptionMock]) + ->willReturnSelf(); + $cartItemMock->expects($this->atLeastOnce())->method('getProductOption')->willReturn($productOptionMock); + $productOptionMock->expects($this->once())->method('setExtensionAttributes')->with($optionExtensionMock); + + $this->assertSame($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } + + public function testProcessProductOptionsInvalidType() + { + $cartItemMock = $this->getMock('\Magento\Quote\Model\Quote\Item', ['getProductType'], [], '', false); + $cartItemMock->expects($this->once())->method('getProductType')->willReturn(Type::TYPE_SIMPLE); + $this->assertSame($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php index d2ad3414e675910c84f4d22879422d1aa0388da6..6807d089307a899c2022aa6272c87afc74e1930c 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/PriceTest.php @@ -77,13 +77,6 @@ class PriceTest extends \PHPUnit_Framework_TestCase $this->priceCurrency = $this->getMockBuilder('Magento\Framework\Pricing\PriceCurrencyInterface')->getMock(); $this->groupManagement = $this->getMockBuilder('Magento\Customer\Api\GroupManagementInterface') ->getMockForAbstractClass(); - $gpFactory = $this->getMock( - 'Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory', - ['create'], - [], - '', - false - ); $tpFactory = $this->getMock( 'Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory', ['create'], @@ -104,7 +97,6 @@ class PriceTest extends \PHPUnit_Framework_TestCase 'eventManager' => $this->eventManagerMock, 'priceCurrency' => $this->priceCurrency, 'groupManagement' => $this->groupManagement, - 'groupPriceFactory' => $gpFactory, 'tierPriceFactory' => $tpFactory, 'config' => $scopeConfig, 'catalogData' => $this->catalogHelperMock diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php index c0af9e9e41d66b6f6ae7b5b6b0f0e16a75d8388f..10bc35a912b2579461bf1060d0b701bee25a4247 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php @@ -1900,7 +1900,8 @@ class TypeTest extends \PHPUnit_Framework_TestCase 'setPositionOrder', 'addFilterByRequiredOptions', 'setSelectionIdsFilter', - 'joinPrices' + 'joinPrices', + 'getItems' ] ) ->disableOriginalConstructor() @@ -1969,6 +1970,10 @@ class TypeTest extends \PHPUnit_Framework_TestCase ->method('setSelectionIdsFilter') ->with($selectionIds) ->will($this->returnSelf()); + $usedSelectionsMock->expects($this->once()) + ->method('getItems') + ->willReturn($usedSelectionsIds); + $usedSelectionsMock->expects($this->once()) ->method('joinPrices') ->with($websiteId) @@ -1981,6 +1986,96 @@ class TypeTest extends \PHPUnit_Framework_TestCase $this->model->getSelectionsByIds($selectionIds, $productMock); } + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The options you selected are not available. + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testGetSelectionsByIdsException() + { + $selectionIds = [1, 2, 3]; + $usedSelectionsIds = [4, 5]; + $storeId = 2; + $storeFilter = 'store_filter'; + $productMock = $this->getMockBuilder('Magento\Catalog\Model\Product') + ->disableOriginalConstructor() + ->getMock(); + $usedSelectionsMock = $this->getMockBuilder('Magento\Bundle\Model\Resource\Selection\Collection') + ->setMethods( + [ + 'addAttributeToSelect', + 'setFlag', + 'addStoreFilter', + 'setStoreId', + 'setPositionOrder', + 'addFilterByRequiredOptions', + 'setSelectionIdsFilter', + 'joinPrices', + 'getItems' + ] + ) + ->disableOriginalConstructor() + ->getMock(); + $productGetMap = [ + ['_cache_instance_used_selections', null, null], + ['_cache_instance_used_selections_ids', null, $usedSelectionsIds], + ['_cache_instance_store_filter', null, $storeFilter], + ]; + $productMock->expects($this->any()) + ->method('getData') + ->will($this->returnValueMap($productGetMap)); + $productSetMap = [ + ['_cache_instance_used_selections', $usedSelectionsMock, $productMock], + ['_cache_instance_used_selections_ids', $selectionIds, $productMock], + ]; + $productMock->expects($this->any()) + ->method('setData') + ->will($this->returnValueMap($productSetMap)); + $productMock->expects($this->once()) + ->method('getStoreId') + ->will($this->returnValue($storeId)); + + $this->bundleCollection->expects($this->once()) + ->method('create') + ->will($this->returnValue($usedSelectionsMock)); + + $usedSelectionsMock->expects($this->once()) + ->method('addAttributeToSelect') + ->with('*') + ->will($this->returnSelf()); + $flagMap = [ + ['require_stock_items', true, $usedSelectionsMock], + ['product_children', true, $usedSelectionsMock], + ]; + $usedSelectionsMock->expects($this->any()) + ->method('setFlag') + ->will($this->returnValueMap($flagMap)); + $usedSelectionsMock->expects($this->once()) + ->method('addStoreFilter') + ->with($storeFilter) + ->will($this->returnSelf()); + $usedSelectionsMock->expects($this->once()) + ->method('setStoreId') + ->with($storeId) + ->will($this->returnSelf()); + $usedSelectionsMock->expects($this->once()) + ->method('setPositionOrder') + ->will($this->returnSelf()); + $usedSelectionsMock->expects($this->once()) + ->method('addFilterByRequiredOptions') + ->will($this->returnSelf()); + $usedSelectionsMock->expects($this->once()) + ->method('setSelectionIdsFilter') + ->with($selectionIds) + ->will($this->returnSelf()); + $usedSelectionsMock->expects($this->once()) + ->method('getItems') + ->willReturn($usedSelectionsIds); + + + $this->model->getSelectionsByIds($selectionIds, $productMock); + } /** * @return void */ diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/GroupPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/GroupPriceTest.php deleted file mode 100644 index cca5cb8a6efb16f42a68fe33cae36efd353cc243..0000000000000000000000000000000000000000 --- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/GroupPriceTest.php +++ /dev/null @@ -1,221 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -namespace Magento\Bundle\Test\Unit\Pricing\Price; - -class GroupPriceTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Magento\Bundle\Pricing\Price\GroupPrice - */ - protected $groupPrice; - - /** - * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject - */ - protected $productMock; - - /** - * @var \Magento\Catalog\Model\Resource\Product|\PHPUnit_Framework_MockObject_MockObject - */ - protected $productResourceMock; - - /** - * @var \Magento\Framework\Pricing\Adjustment\Calculator|\PHPUnit_Framework_MockObject_MockObject - */ - protected $calculatorMock; - - /** - * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerSessionMock; - - /** - * @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerMock; - - /** - * @var \Magento\Catalog\Model\Entity\Attribute|\PHPUnit_Framework_MockObject_MockObject - */ - protected $attributeMock; - - /** - * @var \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice|\PHPUnit_Framework_MockObject_MockObject - */ - protected $backendMock; - - /** - * @var \Magento\Framework\Pricing\PriceInfo\Base|\PHPUnit_Framework_MockObject_MockObject - */ - protected $priceInfoMock; - - /** - * @var \Magento\Catalog\Pricing\Price\RegularPrice|\PHPUnit_Framework_MockObject_MockObject - */ - protected $regularPrice; - - /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $priceCurrencyMock; - - /** - * Set up test case - */ - public function setUp() - { - $this->productMock = $this->getMock( - 'Magento\Catalog\Model\Product', - ['__wakeup', 'getCustomerGroupId', 'getPriceInfo', 'getResource', 'getData'], - [], - '', - false - ); - $this->productResourceMock = $this->getMock( - 'Magento\Catalog\Model\Resource\Product', - [], - [], - '', - false - ); - $this->calculatorMock = $this->getMock( - 'Magento\Framework\Pricing\Adjustment\Calculator', - [], - [], - '', - false - ); - $this->customerSessionMock = $this->getMock( - 'Magento\Customer\Model\Session', - [], - [], - '', - false - ); - $this->customerMock = $this->getMock( - 'Magento\Customer\Model\Customer', - [], - [], - '', - false - ); - $this->attributeMock = $this->getMock( - 'Magento\Catalog\Model\Entity\Attribute', - [], - [], - '', - false - ); - $this->backendMock = $this->getMock( - 'Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice', - [], - [], - '', - false - ); - $this->priceInfoMock = $this->getMock( - 'Magento\Framework\Pricing\PriceInfo\Base', - ['getPrice'], - [], - '', - false - ); - $this->regularPrice = $this->getMock( - 'Magento\Catalog\Pricing\Price\RegularPrice', - [], - [], - '', - false - ); - $this->productMock->expects($this->once()) - ->method('getPriceInfo') - ->will($this->returnValue($this->priceInfoMock)); - - $this->priceCurrencyMock = $this->getMock('\Magento\Framework\Pricing\PriceCurrencyInterface'); - - $this->groupPrice = new \Magento\Bundle\Pricing\Price\GroupPrice( - $this->productMock, - 1, - $this->calculatorMock, - $this->priceCurrencyMock, - $this->customerSessionMock - ); - } - - public function testGetValue() - { - $this->priceInfoMock->expects($this->once()) - ->method('getPrice') - ->with($this->equalTo('regular_price')) - ->will($this->returnValue($this->regularPrice)); - $this->regularPrice->expects($this->once()) - ->method('getValue') - ->will($this->returnValue(100)); - $this->productMock->expects($this->once()) - ->method('getCustomerGroupId') - ->will($this->returnValue(null)); - $this->customerSessionMock->expects($this->once()) - ->method('getCustomerGroupId') - ->will($this->returnValue(3)); - $this->productMock->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($this->productResourceMock)); - $this->productResourceMock->expects($this->once()) - ->method('getAttribute') - ->with($this->equalTo('group_price')) - ->will($this->returnValue($this->attributeMock)); - $this->attributeMock->expects($this->once()) - ->method('getBackend') - ->will($this->returnValue($this->backendMock)); - $this->backendMock->expects($this->once()) - ->method('afterLoad') - ->with($this->equalTo($this->productMock)) - ->will($this->returnValue($this->backendMock)); - $this->priceCurrencyMock->expects($this->never()) - ->method('convertAndRound'); - $this->productMock->expects($this->once()) - ->method('getData') - ->with( - $this->equalTo('group_price'), - $this->equalTo(null) - ) - ->will( - $this->returnValue( - [ - [ - 'cust_group' => 3, - 'website_price' => 80, - ], - ] - - ) - ); - $this->assertEquals(20, $this->groupPrice->getValue()); - $this->assertEquals(20, $this->groupPrice->getValue()); - } - - public function testGetValueNotGroupPrice() - { - $this->productMock->expects($this->once()) - ->method('getCustomerGroupId') - ->will($this->returnValue(null)); - $this->customerSessionMock->expects($this->once()) - ->method('getCustomerGroupId') - ->will($this->returnValue(3)); - $this->productMock->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($this->productResourceMock)); - $this->productResourceMock->expects($this->once()) - ->method('getAttribute') - ->with($this->equalTo('group_price')) - ->will($this->returnValue(null)); - - $this->assertFalse($this->groupPrice->getValue()); - } -} diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index 858d153d85dd8aaadf21aa99e8535ed6c1a310d9..31a87838890f8eef251744b8e915bff4b48cb999 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -13,6 +13,7 @@ <preference for="Magento\Bundle\Api\ProductOptionRepositoryInterface" type="Magento\Bundle\Model\OptionRepository" /> <preference for="Magento\Bundle\Api\ProductOptionManagementInterface" type="Magento\Bundle\Model\OptionManagement" /> <preference for="Magento\Bundle\Api\Data\OptionInterface" type="Magento\Bundle\Model\Option" /> + <preference for="Magento\Bundle\Api\Data\BundleOptionInterface" type="Magento\Bundle\Model\BundleOption" /> <type name="Magento\Bundle\Model\Source\Option\Type"> <arguments> <argument name="options" xsi:type="array"> @@ -44,7 +45,6 @@ <item name="regular_price" xsi:type="string">Magento\Bundle\Pricing\Price\BundleRegularPrice</item> <item name="final_price" xsi:type="string">Magento\Bundle\Pricing\Price\FinalPrice</item> <item name="tier_price" xsi:type="string">Magento\Bundle\Pricing\Price\TierPrice</item> - <item name="group_price" xsi:type="string">Magento\Bundle\Pricing\Price\GroupPrice</item> <item name="special_price" xsi:type="string">Magento\Bundle\Pricing\Price\SpecialPrice</item> <item name="custom_option_price" xsi:type="string">Magento\Catalog\Pricing\Price\CustomOptionPrice</item> <item name="base_price" xsi:type="string">Magento\Catalog\Pricing\Price\BasePrice</item> @@ -102,4 +102,11 @@ <type name="Magento\Quote\Model\Quote\Item\ToOrderItem"> <plugin name="append_bundle_data_to_order" type="Magento\Bundle\Model\Plugin\QuoteItem"/> </type> + <type name="Magento\Quote\Model\Quote\Item\Repository"> + <arguments> + <argument name="cartItemProcessors" xsi:type="array"> + <item name="bundle" xsi:type="object">\Magento\Bundle\Model\CartItemProcessor</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Bundle/etc/extension_attributes.xml b/app/code/Magento/Bundle/etc/extension_attributes.xml index 14e924e1a70bc9c9431a5dc062d45bd2f4022769..48646e276a7298aae7b43fcdcf63ddc251b5e96a 100644 --- a/app/code/Magento/Bundle/etc/extension_attributes.xml +++ b/app/code/Magento/Bundle/etc/extension_attributes.xml @@ -9,4 +9,7 @@ <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface"> <attribute code="bundle_product_options" type="Magento\Bundle\Api\Data\OptionInterface[]" /> </extension_attributes> + <extension_attributes for="Magento\Quote\Api\Data\ProductOptionInterface"> + <attribute code="bundle_options" type="Magento\Bundle\Api\Data\BundleOptionInterface[]" /> + </extension_attributes> </config> diff --git a/app/code/Magento/Bundle/etc/module.xml b/app/code/Magento/Bundle/etc/module.xml index 5f435d51c45dd54015378972690cabbc0f0dd9da..133212807a9be24fc67c63f33342c8e266bb6580 100644 --- a/app/code/Magento/Bundle/etc/module.xml +++ b/app/code/Magento/Bundle/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> - <module name="Magento_Bundle" setup_version="2.0.0"> + <module name="Magento_Bundle" setup_version="2.0.1"> <sequence> <module name="Magento_Catalog"/> </sequence> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml index 91aec2386ca0c72d225a8b7ae41f4ce90d618c55..67b5e66322655372eabf4f5e34bb33ed3983ccbe 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/edit/bundle.phtml @@ -16,40 +16,29 @@ if(typeof Bundle=='undefined') { </script> -<div - id="bundle_product_container" - class="entry-edit form-inline"> - <div class="fieldset-wrapper admin__collapsible-block-wrapper collapsible-block-wrapper-last" id="bundle_product-wrapper"> - <div class="fieldset-wrapper-title"> - <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#bundle_product-content"> - <span><?php /* @escapeNotVerified */ echo $block->getTabLabel() ?></span> - </strong> +<div id="bundle_product_container" class="entry-edit form-inline"> + <fieldset class="fieldset"> + <div class="field field-ship-bundle-items"> + <label for="shipment_type" class="label"><?php /* @escapeNotVerified */ echo __('Ship Bundle Items') ?></label> + <div class="control"> + <select <?php if ($block->isReadonly()): ?>disabled="disabled" <?php endif;?> + id="shipment_type" + name="<?php /* @escapeNotVerified */ echo $block->getFieldSuffix() ?>[shipment_type]" + class="select"> + <option value="1"><?php /* @escapeNotVerified */ echo __('Separately') ?></option> + <option value="0"<?php if ($block->getProduct()->getShipmentType() == 0): ?> selected="selected"<?php endif; ?>><?php /* @escapeNotVerified */ echo __('Together') ?></option> + </select> + </div> </div> - <div class="fieldset-wrapper-content collapse" id="bundle_product-content"> - <fieldset class="fieldset"> - <div class="field field-ship-bundle-items"> - <label for="shipment_type" class="label"><?php /* @escapeNotVerified */ echo __('Ship Bundle Items') ?></label> - <div class="control"> - <select <?php if ($block->isReadonly()): ?>disabled="disabled" <?php endif;?> - id="shipment_type" - name="<?php /* @escapeNotVerified */ echo $block->getFieldSuffix() ?>[shipment_type]" - class="select"> - <option value="1"><?php /* @escapeNotVerified */ echo __('Separately') ?></option> - <option value="0"<?php if ($block->getProduct()->getShipmentType() == 0): ?> selected="selected"<?php endif; ?>><?php /* @escapeNotVerified */ echo __('Together') ?></option> - </select> - </div> - </div> - <div - id="product_bundle_container" - class="entry-edit custom-options bundle sortable-wrapper"> - <?php echo $block->getOptionsBoxHtml() ?> - </div> - - <?php echo $block->getAddButtonHtml() ?> - </fieldset> + <div + id="product_bundle_container" + class="entry-edit custom-options bundle sortable-wrapper"> + <?php echo $block->getOptionsBoxHtml() ?> </div> - </div> + + <?php echo $block->getAddButtonHtml() ?> + </fieldset> </div> <input type="hidden" name="affect_bundle_product_selections" value="1" /> diff --git a/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css b/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css index 1e523be9723ffd2e1d378a4764c05e8b769981fd..47e9555984bae83aedc75c8462af215d3c7d9868 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css +++ b/app/code/Magento/Bundle/view/adminhtml/web/css/bundle-product.css @@ -78,9 +78,27 @@ } .field-weight .weight { - width: 140px; + display: inline-block; margin-right: 15px; - float: left; + vertical-align: top; + width: 140px; +} + +.field-weight .weight .admin__control-addon .input-text { + min-width: 10rem; +} + +.field-weight .weight .admin__control-addon .input-text, +.field-weight .weight .admin__control-addon .admin__addon-suffix { + flex-basis: 10rem; +} + +.ie9 .field-weight .weight .admin__control-addon .input-text { + width: 100px; +} + +.weight-switcher .admin__control-addon { + width: 100%; } .field-weight .weight .field:first-child { diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js b/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js index 5b6515db33fa5a13be2a3493cbd7588b36cda191..4d4c1526c8aa49835fff12ac7b7d91a3bdef22a8 100644 --- a/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/bundle-product.js @@ -8,11 +8,12 @@ /*global $H*/ define([ "jquery", + "Magento_Catalog/js/product/weight-handler", "jquery/ui", "mage/translate", "Magento_Theme/js/sortable", "prototype" -], function($){ +], function ($, weightHandler) { 'use strict'; $.widget('mage.bundleProduct', { @@ -188,7 +189,7 @@ define([ return this; }, _hideProductTypeSwitcher: function () { - $('#weight_and_type_switcher, label[for=weight_and_type_switcher]').hide(); + weightHandler.hideWeightSwitcher(); }, _bindCheckboxHandlers: function () { this._on({ diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml index 40f152cf2581d7a3cd05841f0aca24eb1e7a4941..717bf12da25a03ac35628b16738660bfa929e344 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/options.phtml @@ -25,7 +25,7 @@ $helper = $this->helper('Magento\Catalog\Helper\Output'); } } </script> - <fieldset class="fieldset bundle options"> + <fieldset class="fieldset fieldset-bundle-options"> <legend id="customizeTitle" class="legend title"> <span><?php /* @escapeNotVerified */ echo __('Customize %1', $helper->productAttribute($product, $product->getName(), 'name')) ?></span> </legend><br /> diff --git a/app/code/Magento/Catalog/Api/Data/ProductAttributeMediaGalleryEntryInterface.php b/app/code/Magento/Catalog/Api/Data/ProductAttributeMediaGalleryEntryInterface.php index 74116175d682a0f6645240cc17cf706e9f6e5588..5c6a16aa58bfb3abf6f55d38a2a4556d5bdb14a9 100644 --- a/app/code/Magento/Catalog/Api/Data/ProductAttributeMediaGalleryEntryInterface.php +++ b/app/code/Magento/Catalog/Api/Data/ProductAttributeMediaGalleryEntryInterface.php @@ -19,6 +19,7 @@ interface ProductAttributeMediaGalleryEntryInterface extends ExtensibleDataInter const POSITION = 'position'; const DISABLED = 'disabled'; const TYPES = 'types'; + const MEDIA_TYPE = 'media_type'; const FILE = 'file'; const CONTENT = 'content'; @@ -37,6 +38,21 @@ interface ProductAttributeMediaGalleryEntryInterface extends ExtensibleDataInter */ public function setId($id); + /** + * Get media type + * + * @return string + */ + public function getMediaType(); + + /** + * Set media type + * + * @param string $mediaType + * @return $this + */ + public function setMediaType($mediaType); + /** * Retrieve gallery entry alternative text * diff --git a/app/code/Magento/Catalog/Api/Data/ProductGroupPriceInterface.php b/app/code/Magento/Catalog/Api/Data/ProductGroupPriceInterface.php deleted file mode 100644 index d52f42e4f147aba603f154f97d6e05aa4c535fd6..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Api/Data/ProductGroupPriceInterface.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -/** - * Group Price - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Api\Data; - -use Magento\Framework\Api\ExtensibleDataInterface; - -/** - * @api - */ -interface ProductGroupPriceInterface extends ExtensibleDataInterface -{ - /** - * Retrieve customer group id - * - * @return int - */ - public function getCustomerGroupId(); - - /** - * Set customer group id - * - * @param int $customerGroupId - * @return $this - */ - public function setCustomerGroupId($customerGroupId); - - /** - * Retrieve price value - * - * @return float - */ - public function getValue(); - - /** - * Set price value - * - * @param float $value - * @return $this - */ - public function setValue($value); - - /** - * Retrieve existing extension attributes object. - * - * @return \Magento\Catalog\Api\Data\ProductGroupPriceExtensionInterface|null - */ - public function getExtensionAttributes(); - - /** - * Set an extension attributes object. - * - * @param \Magento\Catalog\Api\Data\ProductGroupPriceExtensionInterface $extensionAttributes - * @return $this - */ - public function setExtensionAttributes( - \Magento\Catalog\Api\Data\ProductGroupPriceExtensionInterface $extensionAttributes - ); -} diff --git a/app/code/Magento/Catalog/Api/Data/ProductInterface.php b/app/code/Magento/Catalog/Api/Data/ProductInterface.php index 52322310c3372405a59efd61e5142ccdc3835fe8..cdf7f2f20873481d454d2f4249feecf8296f7f7e 100644 --- a/app/code/Magento/Catalog/Api/Data/ProductInterface.php +++ b/app/code/Magento/Catalog/Api/Data/ProductInterface.php @@ -278,21 +278,6 @@ interface ProductInterface extends \Magento\Framework\Api\CustomAttributesDataIn */ public function setMediaGalleryEntries(array $mediaGalleryEntries = null); - /** - * Gets list of product group prices - * - * @return \Magento\Catalog\Api\Data\ProductGroupPriceInterface[]|null - */ - public function getGroupPrices(); - - /** - * Sets list of product group prices - * - * @param \Magento\Catalog\Api\Data\ProductGroupPriceInterface[] $groupPrices - * @return $this - */ - public function setGroupPrices(array $groupPrices = null); - /** * Gets list of product tier prices * diff --git a/app/code/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterface.php b/app/code/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterface.php index 69074013f6a2b057a3ab101a74530caaf5643a03..5dd3602624138e7f6affc1c6d192e921dcbf3c81 100644 --- a/app/code/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterface.php +++ b/app/code/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterface.php @@ -62,11 +62,11 @@ interface ProductAttributeMediaGalleryManagementInterface * Return information about gallery entry * * @param string $sku - * @param int $imageId + * @param int $entryId * @throws \Magento\Framework\Exception\NoSuchEntityException * @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface */ - public function get($sku, $imageId); + public function get($sku, $entryId); /** * Retrieve the list of gallery entries associated with given product diff --git a/app/code/Magento/Catalog/Api/ProductGroupPriceManagementInterface.php b/app/code/Magento/Catalog/Api/ProductGroupPriceManagementInterface.php deleted file mode 100644 index 60f7b7ef93e4d33507ce2c6cbde6d8ddd46bf8c7..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Api/ProductGroupPriceManagementInterface.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Api; - -/** - * @api - */ -interface ProductGroupPriceManagementInterface -{ - /** - * Set group price for product - * - * @param string $sku - * @param int $customerGroupId - * @param float $price - * @return boolean - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\CouldNotSaveException - */ - public function add($sku, $customerGroupId, $price); - - /** - * Remove group price from product - * - * @param string $sku - * @param int $customerGroupId - * @return boolean - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\CouldNotSaveException - */ - public function remove($sku, $customerGroupId); - - /** - * Retrieve list of product prices - * - * @param string $sku - * @return \Magento\Catalog\Api\Data\ProductGroupPriceInterface[] - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function getList($sku); -} diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php index 6dd100f0eca6d2d6dcbbe2fba2a36200ec279fde..fac304750b366df0d248d8b86c883dbb935b7c31 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit.php @@ -8,6 +8,7 @@ * Customer edit block * * @author Magento Core Team <core@magentocommerce.com> + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ namespace Magento\Catalog\Block\Adminhtml\Product; @@ -310,7 +311,8 @@ class Edit extends \Magento\Backend\Block\Widget return $this->jsonEncoder->encode( [ 'tab_id' => 'product_info_tabs_downloadable_items', - 'is_virtual_id' => \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight::VIRTUAL_FIELD_HTML_ID, + 'weight_switcher' => '[data-role=weight-switcher]', + 'product_has_weight_flag' => \Magento\Catalog\Model\Product\Edit\WeightResolver::HAS_WEIGHT, 'weight_id' => 'weight', 'current_type' => $this->getProduct()->getTypeId(), 'attributes' => $this->_getAttributes(), diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php index 3f5f4d9f3a94cf2985572645d0fa69fc33e0308d..cdc49e5939364318b995a713992936eebeca193d 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php @@ -70,7 +70,6 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements [ 'category_ids', 'gallery', - 'group_price', 'image', 'media_gallery', 'quantity_and_stock_status', @@ -145,8 +144,8 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements HTML; if ($elementId === 'weight') { $html .= <<<HTML -<script>require(["jquery"],function($) { - $('#weight_and_type_switcher, label[for=weight_and_type_switcher]').hide(); +<script>require(['Magento_Catalog/js/product/weight-handler'], function (weightHandle) { + weightHandle.hideWeightSwitcher(); });</script> HTML; } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes.php index c56b87efe5c2480b0fef8652ae5b092183a316a8..0875c56f1f098f40524f827c2b0a7fb108f19700 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Attributes.php @@ -57,13 +57,6 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form ); } - $groupPrice = $form->getElement('group_price'); - if ($groupPrice) { - $groupPrice->setRenderer( - $this->getLayout()->createBlock('Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price\Group') - ); - } - // Add new attribute controls if it is not an image tab if (!$form->getElement( 'media_gallery' diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/ChildTab.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/ChildTab.php new file mode 100644 index 0000000000000000000000000000000000000000..e9b2f68bfb2059f072f7f9d4df5feb61a6864d26 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/ChildTab.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * Product Child tab + */ +namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab; + +class ChildTab extends \Magento\Backend\Block\Template +{ + /** + * @var \Magento\Backend\Block\Widget\Tab\TabInterface + */ + protected $tab; + + /** + * @param \Magento\Backend\Block\Widget\Tab\TabInterface $tab + * @return $this + */ + public function setTab(\Magento\Backend\Block\Widget\Tab\TabInterface $tab) + { + $this->tab = $tab; + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->tab->getTabTitle(); + } + + /** + * @return string + */ + public function getContent() + { + return $this->tab->toHtml(); + } + + /** + * @return string + */ + public function getTabId() + { + return $this->tab->getTabId(); + } + + /** + * @return bool + */ + public function isTabOpened() + { + return (bool)$this->tab->getData('opened'); + } +} diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php index 72ad16576f1ca238d01a068f5cf53be714755b30..5cf7b922ac980d1a20c5be02b284b946dc109d22 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Inventory.php @@ -134,13 +134,14 @@ class Inventory extends \Magento\Backend\Block\Widget public function getFieldValue($field) { $stockItem = $this->getStockItem(); + $value = null; if ($stockItem->getItemId()) { $method = 'get' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($field); - if (method_exists($stockItem, $method)) { - return $stockItem->{$method}(); + if (is_callable([$stockItem, $method])) { + $value = $stockItem->{$method}(); } } - return $this->stockConfiguration->getDefaultConfigValue($field); + return $value === null ? $this->stockConfiguration->getDefaultConfigValue($field) : $value; } /** diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/AbstractType.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/AbstractType.php index b4c8d96bbdc11a4d47cafb77d8121a9c46d40e52..734fe6bc0db2b7d667c742cf2753d8e868f2255a 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/AbstractType.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/AbstractType.php @@ -70,13 +70,17 @@ class AbstractType extends \Magento\Backend\Block\Widget /** * Get html of Price Type select element * + * @param string $extraParams * @return string */ - public function getPriceTypeSelectHtml() + public function getPriceTypeSelectHtml($extraParams = '') { if ($this->getCanEditPrice() === false) { - $this->getChildBlock('option_price_type')->setExtraParams('disabled="disabled"'); + $extraParams .= ' disabled="disabled"'; + $this->getChildBlock('option_price_type'); } + $this->getChildBlock('option_price_type')->setExtraParams($extraParams); + return $this->getChildHtml('option_price_type'); } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/Select.php index c91c09dcb9f15fe552930a1bd7a4e03bd3350deb..621420cfecec31e64c9d3b5a0ef8565c57bf8cac 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/Select.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Type/Select.php @@ -90,8 +90,8 @@ class Select extends \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\T 'product_option_<%- data.id %>_select_<%- data.select_id %>_price_type' )->setName( 'product[options][<%- data.id %>][values][<%- data.select_id %>][price_type]' - )->setExtraParams($extraParams); + ); - return parent::getPriceTypeSelectHtml(); + return parent::getPriceTypeSelectHtml($extraParams); } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Group.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Group.php deleted file mode 100644 index 28f5959e9f870522c2398cde45a183f1e3d2d575..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Group.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price; - -use Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price\Group\AbstractGroup; - -/** - * Adminhtml group price item renderer - */ -class Group extends AbstractGroup -{ - /** - * @var string - */ - protected $_template = 'catalog/product/edit/price/group.phtml'; - - /** - * Sort values - * - * @param array $data - * @return array - */ - protected function _sortValues($data) - { - usort($data, [$this, '_sortGroupPrices']); - return $data; - } - - /** - * Sort group price values callback method - * - * @param array $a - * @param array $b - * @return int - */ - protected function _sortGroupPrices($a, $b) - { - if ($a['website_id'] != $b['website_id']) { - return $a['website_id'] < $b['website_id'] ? -1 : 1; - } - if ($a['cust_group'] != $b['cust_group']) { - return $this->getCustomerGroups($a['cust_group']) < $this->getCustomerGroups($b['cust_group']) ? -1 : 1; - } - return 0; - } - - /** - * Prepare global layout - * - * Add "Add Group Price" button to layout - * - * @return $this - */ - protected function _prepareLayout() - { - $button = $this->getLayout()->createBlock( - 'Magento\Backend\Block\Widget\Button' - )->setData( - [ - 'label' => __('Add Group Price'), - 'onclick' => 'return groupPriceControl.addItem()', - 'class' => 'add', - ] - ); - $button->setName('add_group_price_item_button'); - - $this->setChild('add_button', $button); - return parent::_prepareLayout(); - } -} diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Tier.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Tier.php index d2c9e742545cd88859eb5e04d81ed80c5e1d1804..f1f84f2d7573716acc112877771af1439592c037 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Tier.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Price/Tier.php @@ -71,7 +71,7 @@ class Tier extends Group\AbstractGroup $button = $this->getLayout()->createBlock( 'Magento\Backend\Block\Widget\Button' )->setData( - ['label' => __('Add Tier'), 'onclick' => 'return tierPriceControl.addItem()', 'class' => 'add'] + ['label' => __('Add Price'), 'onclick' => 'return tierPriceControl.addItem()', 'class' => 'add'] ); $button->setName('add_tier_price_item_button'); diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php index 2f99d45e092e8458fd1c6d6c38eb9cd26becd14c..f775a6901ffe580f7a56f350d7ab724cfcf410c6 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php @@ -4,12 +4,25 @@ * See COPYING.txt for license details. */ +namespace Magento\Catalog\Block\Adminhtml\Product\Edit; + +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Block\Widget\Accordion; +use Magento\Backend\Block\Widget\Tabs as WigetTabs; +use Magento\Backend\Model\Auth\Session; +use Magento\Catalog\Helper\Catalog; +use Magento\Catalog\Helper\Data; +use Magento\Eav\Model\Resource\Entity\Attribute\Group\CollectionFactory; +use Magento\Framework\Json\EncoderInterface; +use Magento\Framework\Module\Manager; +use Magento\Framework\Registry; +use Magento\Framework\Translate\InlineInterface; + /** * Admin product edit tabs + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -namespace Magento\Catalog\Block\Adminhtml\Product\Edit; - -class Tabs extends \Magento\Backend\Block\Widget\Tabs +class Tabs extends WigetTabs { const BASIC_TAB_GROUP_CODE = 'basic'; @@ -28,62 +41,62 @@ class Tabs extends \Magento\Backend\Block\Widget\Tabs /** * Core registry * - * @var \Magento\Framework\Registry + * @var Registry */ protected $_coreRegistry = null; /** * Catalog data * - * @var \Magento\Catalog\Helper\Data + * @var Data */ protected $_catalogData = null; /** * Adminhtml catalog * - * @var \Magento\Catalog\Helper\Catalog + * @var Catalog */ protected $_helperCatalog = null; /** - * @var \Magento\Eav\Model\Resource\Entity\Attribute\Group\CollectionFactory + * @var CollectionFactory */ protected $_collectionFactory; /** - * @var \Magento\Framework\Module\Manager + * @var Manager */ protected $_moduleManager; /** - * @var \Magento\Framework\Translate\InlineInterface + * @var InlineInterface */ protected $_translateInline; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder - * @param \Magento\Backend\Model\Auth\Session $authSession - * @param \Magento\Framework\Module\Manager $moduleManager - * @param \Magento\Eav\Model\Resource\Entity\Attribute\Group\CollectionFactory $collectionFactory - * @param \Magento\Catalog\Helper\Catalog $helperCatalog - * @param \Magento\Catalog\Helper\Data $catalogData - * @param \Magento\Framework\Registry $registry - * @param \Magento\Framework\Translate\InlineInterface $translateInline + * @param Context $context + * @param EncoderInterface $jsonEncoder + * @param Session $authSession + * @param Manager $moduleManager + * @param CollectionFactory $collectionFactory + * @param Catalog $helperCatalog + * @param Data $catalogData + * @param Registry $registry + * @param InlineInterface $translateInline * @param array $data * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Framework\Json\EncoderInterface $jsonEncoder, - \Magento\Backend\Model\Auth\Session $authSession, - \Magento\Framework\Module\Manager $moduleManager, - \Magento\Eav\Model\Resource\Entity\Attribute\Group\CollectionFactory $collectionFactory, - \Magento\Catalog\Helper\Catalog $helperCatalog, - \Magento\Catalog\Helper\Data $catalogData, - \Magento\Framework\Registry $registry, - \Magento\Framework\Translate\InlineInterface $translateInline, + Context $context, + EncoderInterface $jsonEncoder, + Session $authSession, + Manager $moduleManager, + CollectionFactory $collectionFactory, + Catalog $helperCatalog, + Data $catalogData, + Registry $registry, + InlineInterface $translateInline, array $data = [] ) { $this->_moduleManager = $moduleManager; @@ -189,15 +202,15 @@ class Tabs extends \Magento\Backend\Block\Widget\Tabs unset($advancedGroups['advanced-pricing']); } - if ($this->_moduleManager->isEnabled('Magento_CatalogInventory')) { + if ($this->_moduleManager->isEnabled('Magento_CatalogInventory') + && $this->getChildBlock('advanced-inventory') + ) { $this->addTab( 'advanced-inventory', [ 'label' => __('Advanced Inventory'), 'content' => $this->_translateHtml( - $this->getLayout()->createBlock( - 'Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Inventory' - )->toHtml() + $this->getChildHtml('advanced-inventory') ), 'group_code' => self::ADVANCED_TAB_GROUP_CODE ] @@ -322,4 +335,19 @@ class Tabs extends \Magento\Backend\Block\Widget\Tabs $this->_translateInline->processResponseBody($html); return $html; } + + /** + * @param string $parentTab + * @return string + */ + public function getAccordion($parentTab) + { + $html = ''; + foreach ($this->_tabs as $childTab) { + if ($childTab->getParentTab() === $parentTab->getId()) { + $html .= $this->getChildBlock('child-tab')->setTab($childTab)->toHtml(); + } + } + return $html; + } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/BaseImage.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/BaseImage.php index 4d125f4194f3cdc0a6821f81390dda906c328cd1..68ceb73cccaa16613417fbe9dc84ca2a2c6978f1 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/BaseImage.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/BaseImage.php @@ -11,29 +11,42 @@ */ namespace Magento\Catalog\Block\Adminhtml\Product\Helper\Form; +/** + * Class BaseImage + */ class BaseImage extends \Magento\Framework\Data\Form\Element\AbstractElement { + /** + * Element output template + */ + const ELEMENT_OUTPUT_TEMPLATE = 'Magento_Catalog::product/edit/base_image.phtml'; + /** * Model Url instance * * @var \Magento\Backend\Model\UrlInterface */ - protected $_url; + protected $url; /** * @var \Magento\Catalog\Helper\Data */ - protected $_catalogHelperData; + protected $catalogHelperData; /** * @var \Magento\Framework\File\Size */ - protected $_fileConfig; + protected $fileConfig; /** * @var \Magento\Framework\View\Asset\Repository */ - protected $_assetRepo; + protected $assetRepo; + + /** + * @var \Magento\Framework\View\LayoutInterface + */ + protected $layout; /** * @param \Magento\Framework\Data\Form\Element\Factory $factoryElement @@ -43,6 +56,7 @@ class BaseImage extends \Magento\Framework\Data\Form\Element\AbstractElement * @param \Magento\Backend\Model\UrlFactory $backendUrlFactory * @param \Magento\Catalog\Helper\Data $catalogData * @param \Magento\Framework\File\Size $fileConfig + * @param \Magento\Framework\View\LayoutInterface $layout * @param array $data */ public function __construct( @@ -53,15 +67,17 @@ class BaseImage extends \Magento\Framework\Data\Form\Element\AbstractElement \Magento\Backend\Model\UrlFactory $backendUrlFactory, \Magento\Catalog\Helper\Data $catalogData, \Magento\Framework\File\Size $fileConfig, + \Magento\Framework\View\LayoutInterface $layout, array $data = [] ) { parent::__construct($factoryElement, $factoryCollection, $escaper, $data); - $this->_assetRepo = $assetRepo; - $this->_url = $backendUrlFactory->create(); - $this->_catalogHelperData = $catalogData; - $this->_fileConfig = $fileConfig; - $this->_maxFileSize = $this->_getFileMaxSize(); + $this->assetRepo = $assetRepo; + $this->url = $backendUrlFactory->create(); + $this->catalogHelperData = $catalogData; + $this->fileConfig = $fileConfig; + $this->maxFileSize = $this->getFileMaxSize(); + $this->layout = $layout; } /** @@ -71,7 +87,7 @@ class BaseImage extends \Magento\Framework\Data\Form\Element\AbstractElement */ public function getLabel() { - return __('Images'); + return __('Images and Videos'); } /** @@ -81,66 +97,46 @@ class BaseImage extends \Magento\Framework\Data\Form\Element\AbstractElement */ public function getElementHtml() { - $htmlId = $this->_escaper->escapeHtml($this->getHtmlId()); - $uploadUrl = $this->_escaper->escapeHtml($this->_getUploadUrl()); - $spacerImage = $this->_assetRepo->getUrl('images/spacer.gif'); - $imagePlaceholderText = __('Click here or drag and drop to add images.'); - $deleteImageText = __('Delete image'); - $makeBaseText = __('Make Base'); - $hiddenText = __('Hidden'); - $imageManagementText = __('Image Management'); - /** @var $product \Magento\Catalog\Model\Product */ - $html = <<<HTML -<div id="{$htmlId}-container" class="images" - data-mage-init='{"baseImage":{}}' - data-max-file-size="{$this->_getFileMaxSize()}" - > - <div class="image image-placeholder"> - <input type="file" name="image" data-url="{$uploadUrl}" multiple="multiple" /> - <img class="spacer" src="{$spacerImage}"/> - <p class="image-placeholder-text">{$imagePlaceholderText}</p> - </div> - <script id="{$htmlId}-template" data-template="image" type="text/x-magento-template"> - <div class="image"> - <img class="spacer" src="{$spacerImage}"/> - <img - class="product-image" - src="<%- data.url %>" - data-position="<%- data.position %>" - alt="<%- data.label %>" /> - <div class="actions"> - <button type="button" class="action-delete" data-role="delete-button" title="{$deleteImageText}"> - <span>{$deleteImageText}</span> - </button> - <button type="button" class="action-make-base" data-role="make-base-button" title="{$makeBaseText}"> - <span>{$makeBaseText}</span> - </button> - <div class="draggable-handle"></div> - </div> - <div class="image-label"></div> - <div class="image-fade"><span>{$hiddenText}</span></div> - </div> - </script> -</div> -<span class="action-manage-images" data-activate-tab="image-management"> - <span>{$imageManagementText}</span> -</span> -<script> - require([ - 'jquery' - ],function($){ - - 'use strict'; - - $('[data-activate-tab=image-management]') - .on('click.toggleImageManagementTab', function() { - $('#product_info_tabs_image-management').trigger('click'); - }); - }); -</script> - -HTML; - return $html; + $block = $this->createElementHtmlOutputBlock(); + $this->assignBlockVariables($block); + return $block->toHtml(); + } + + /** + * @param \Magento\Framework\View\Element\Template $block + * @return \Magento\Framework\View\Element\Template + */ + public function assignBlockVariables(\Magento\Framework\View\Element\Template $block) + { + $block->assign([ + 'htmlId' => $this->_escaper->escapeHtml($this->getHtmlId()), + 'fileMaxSize' => $this->maxFileSize, + 'uploadUrl' => $this->_escaper->escapeHtml($this->_getUploadUrl()), + 'spacerImage' => $this->assetRepo->getUrl('images/spacer.gif'), + 'imagePlaceholderText' => __('Click here or drag and drop to add images.'), + 'deleteImageText' => __('Delete image'), + 'makeBaseText' => __('Make Base'), + 'hiddenText' => __('Hidden'), + 'imageManagementText' => __('Images and Videos'), + ]); + + return $block; + } + + + /** + * @return \Magento\Framework\View\Element\Template + */ + public function createElementHtmlOutputBlock() + { + /** @var \Magento\Framework\View\Element\Template $block */ + $block = $this->layout->createBlock( + 'Magento\Framework\View\Element\Template', + 'product.details.form.base.image.element' + ); + $block->setTemplate(self::ELEMENT_OUTPUT_TEMPLATE); + + return $block; } /** @@ -150,7 +146,7 @@ HTML; */ protected function _getUploadUrl() { - return $this->_url->getUrl('catalog/product_gallery/upload'); + return $this->url->getUrl('catalog/product_gallery/upload'); } /** @@ -158,8 +154,8 @@ HTML; * * @return int */ - protected function _getFileMaxSize() + protected function getFileMaxSize() { - return $this->_fileConfig->getMaxFileSize(); + return $this->fileConfig->getMaxFileSize(); } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Weight.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Weight.php index 1b787f05ffd5a7d5e084e8b3cbb501bf24798f37..de677ca788b28f5d3d3fd9d393c4da47bec2b09f 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Weight.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Weight.php @@ -6,30 +6,20 @@ /** * Product form weight field helper - * - * @author Magento Core Team <core@magentocommerce.com> */ namespace Magento\Catalog\Block\Adminhtml\Product\Helper\Form; use Magento\Framework\Data\Form; +use Magento\Catalog\Model\Product\Edit\WeightResolver; class Weight extends \Magento\Framework\Data\Form\Element\Text { - const VIRTUAL_FIELD_HTML_ID = 'weight_and_type_switcher'; - /** - * Is virtual checkbox element + * Weight switcher radio-button element * * @var \Magento\Framework\Data\Form\Element\Checkbox */ - protected $_virtual; - - /** - * Catalog helper - * - * @var \Magento\Catalog\Helper\Product - */ - protected $_helper; + protected $weightSwitcher; /** * @var \Magento\Framework\Locale\Format @@ -40,7 +30,6 @@ class Weight extends \Magento\Framework\Data\Form\Element\Text * @param \Magento\Framework\Data\Form\Element\Factory $factoryElement * @param \Magento\Framework\Data\Form\Element\CollectionFactory $factoryCollection * @param \Magento\Framework\Escaper $escaper - * @param \Magento\Catalog\Helper\Product $helper * @param \Magento\Framework\Locale\Format $localeFormat * @param array $data */ @@ -48,48 +37,58 @@ class Weight extends \Magento\Framework\Data\Form\Element\Text \Magento\Framework\Data\Form\Element\Factory $factoryElement, \Magento\Framework\Data\Form\Element\CollectionFactory $factoryCollection, \Magento\Framework\Escaper $escaper, - \Magento\Catalog\Helper\Product $helper, \Magento\Framework\Locale\Format $localeFormat, array $data = [] ) { - $this->_helper = $helper; $this->localeFormat = $localeFormat; - $this->_virtual = $factoryElement->create('checkbox'); - $this->_virtual->setId( - self::VIRTUAL_FIELD_HTML_ID + $this->weightSwitcher = $factoryElement->create('radios'); + $this->weightSwitcher->setValue( + WeightResolver::HAS_WEIGHT + )->setValues( + [ + ['value' => WeightResolver::HAS_WEIGHT, 'label' => __('Yes')], + ['value' => WeightResolver::HAS_NO_WEIGHT, 'label' => __('No')] + ] + )->setId( + 'weight-switcher' )->setName( - 'is_virtual' + 'product_has_weight' )->setLabel( - $this->_helper->getTypeSwitcherControlLabel() + __('Does this have a weight?') ); parent::__construct($factoryElement, $factoryCollection, $escaper, $data); $this->addClass('validate-zero-or-greater'); } /** - * Add Is Virtual checkbox html to weight field + * Add Weight Switcher radio-button element html to weight field * * @return string */ public function getElementHtml() { if (!$this->getForm()->getDataObject()->getTypeInstance()->hasWeight()) { - $this->_virtual->setChecked('checked'); + $this->weightSwitcher->setValue(WeightResolver::HAS_NO_WEIGHT); } if ($this->getDisabled()) { - $this->_virtual->setDisabled($this->getDisabled()); + $this->weightSwitcher->setDisabled($this->getDisabled()); } - return '<div class="fields-group-2"><div class="field"><div class="addon"><div class="control">' . + return '<div class="admin__field-control weight-switcher">' . + '<div class="admin__control-switcher" data-role="weight-switcher">' . + $this->weightSwitcher->getLabelHtml() . + '<div class="admin__field-control-group">' . + $this->weightSwitcher->getElementHtml() . + '</div>' . + '</div>' . + '<div class="admin__control-addon">' . parent::getElementHtml() . - '<label class="addafter" for="' . - $this->getHtmlId() . - '"><strong>' . - __('lbs') . - '</strong></label>' . - '</div></div></div><div class="field choice">' . - $this->_virtual->getElementHtml() . - $this->_virtual->getLabelHtml() . - '</div></div>'; + '<label class="admin__addon-suffix" for="' . + $this->getHtmlId() . + '"><span>' . + __('lbs') . + '</span></label>' . + '</div>' . + '</div>'; } /** @@ -100,7 +99,7 @@ class Weight extends \Magento\Framework\Data\Form\Element\Text */ public function setForm($form) { - $this->_virtual->setForm($form); + $this->weightSwitcher->setForm($form); return parent::setForm($form); } diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php index bfaba7215694333216076f799e490a09a34b14e6..f07a069b38daea127b38797a5564ec1e6ab8207a 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php @@ -160,7 +160,7 @@ class Select extends \Magento\Catalog\Block\Product\View\Options\AbstractOptions $count . '"><span>' . $_value->getTitle() . - '</span>' . + '</span> ' . $priceStr . '</label>'; $selectHtml .= '</div>'; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestProductTemplates.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestProductTemplates.php index 72a29b9183088a4f3329ab8054b93d583f2f2286..d28b72799e770cebf089341b9749125141baefdf 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestProductTemplates.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/SuggestProductTemplates.php @@ -6,7 +6,7 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class SuggestProductTemplates extends \Magento\Catalog\Controller\Adminhtml\Product +class SuggestProductTemplates extends \Magento\Backend\App\Action { /** * @var \Magento\Framework\Controller\Result\JsonFactory @@ -14,25 +14,23 @@ class SuggestProductTemplates extends \Magento\Catalog\Controller\Adminhtml\Prod protected $resultJsonFactory; /** - * @var \Magento\Framework\View\LayoutFactory + * @var \Magento\Catalog\Model\Product\AttributeSet\SuggestedSet */ - protected $layoutFactory; + protected $suggestedSet; /** * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory - * @param \Magento\Framework\View\LayoutFactory $layoutFactory + * @param \Magento\Catalog\Model\Product\AttributeSet\SuggestedSet $suggestedSet */ public function __construct( \Magento\Backend\App\Action\Context $context, - \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, - \Magento\Framework\View\LayoutFactory $layoutFactory + \Magento\Catalog\Model\Product\AttributeSet\SuggestedSet $suggestedSet ) { - parent::__construct($context, $productBuilder); + parent::__construct($context); $this->resultJsonFactory = $resultJsonFactory; - $this->layoutFactory = $layoutFactory; + $this->suggestedSet = $suggestedSet; } /** @@ -42,11 +40,9 @@ class SuggestProductTemplates extends \Magento\Catalog\Controller\Adminhtml\Prod */ public function execute() { - $this->productBuilder->build($this->getRequest()); $resultJson = $this->resultJsonFactory->create(); $resultJson->setData( - $this->layoutFactory->create()->createBlock('Magento\Catalog\Block\Product\TemplateSelector') - ->getSuggestedTemplates($this->getRequest()->getParam('label_part')) + $this->suggestedSet->getSuggestedSets($this->getRequest()->getParam('label_part')) ); return $resultJson; } diff --git a/app/code/Magento/Catalog/Helper/Product.php b/app/code/Magento/Catalog/Helper/Product.php index cc0305c3545cc49163c5b86622b564a4fdc81369..1083bc63a5158298014c016d37084fdf4b8030fd 100644 --- a/app/code/Magento/Catalog/Helper/Product.php +++ b/app/code/Magento/Catalog/Helper/Product.php @@ -52,11 +52,6 @@ class Product extends \Magento\Framework\Url\Helper\Data */ protected $_coreRegistry; - /** - * @var string - */ - protected $_typeSwitcherLabel; - /** * @var \Magento\Catalog\Model\Attribute\Config */ @@ -103,7 +98,6 @@ class Product extends \Magento\Framework\Url\Helper\Data * @param \Magento\Framework\View\Asset\Repository $assetRepo * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Catalog\Model\Attribute\Config $attributeConfig - * @param string $typeSwitcherLabel * @param array $reindexPriceIndexerData * @param array $reindexProductCategoryIndexerData * @param ProductRepositoryInterface $productRepository @@ -117,14 +111,12 @@ class Product extends \Magento\Framework\Url\Helper\Data \Magento\Framework\View\Asset\Repository $assetRepo, \Magento\Framework\Registry $coreRegistry, \Magento\Catalog\Model\Attribute\Config $attributeConfig, - $typeSwitcherLabel, $reindexPriceIndexerData, $reindexProductCategoryIndexerData, ProductRepositoryInterface $productRepository, CategoryRepositoryInterface $categoryRepository ) { $this->_catalogSession = $catalogSession; - $this->_typeSwitcherLabel = $typeSwitcherLabel; $this->_attributeConfig = $attributeConfig; $this->_coreRegistry = $coreRegistry; $this->_assetRepo = $assetRepo; @@ -582,14 +574,4 @@ class Product extends \Magento\Framework\Url\Helper\Data { return $this->_attributeConfig->getAttributeNames('used_in_autogeneration'); } - - /** - * Get label for virtual control - * - * @return \Magento\Framework\Phrase - */ - public function getTypeSwitcherControlLabel() - { - return __($this->_typeSwitcherLabel); - } } diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php index 6fdba349dd044588c29d85c50372ecf815aa407b..b03e7a43c41bb08a79381ad447bab2a59323befa 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository.php @@ -61,6 +61,8 @@ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInter public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) { $existingData = $category->toFlatArray(); + /** 'available_sort_by' should be set separately because fields of array type are destroyed by toFlatArray() */ + $existingData['available_sort_by'] = $category->getAvailableSortBy(); if ($category->getId()) { $existingCategory = $this->get($category->getId()); if (isset($existingData['image']) && is_array($existingData['image'])) { diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index 84a1c9dfc8b28f815c8d197040b9e0e615e93b36..947ce75e627a951a088a7313d1ebd3d64da0fd49 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -251,55 +251,6 @@ abstract class AbstractAction return $this; } - /** - * Prepare group price index table - * - * @param int|array $entityIds the entity ids limitation - * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction - */ - protected function _prepareGroupPriceIndex($entityIds = null) - { - $table = $this->_defaultIndexerResource->getTable('catalog_product_index_group_price'); - $this->_emptyTable($table); - - $websiteExpression = $this->_connection->getCheckSql( - 'gp.website_id = 0', - 'ROUND(gp.value * cwd.rate, 4)', - 'gp.value' - ); - $select = $this->_connection->select()->from( - ['gp' => $this->_defaultIndexerResource->getTable(['catalog_product_entity', 'group_price'])], - ['entity_id'] - )->join( - ['cg' => $this->_defaultIndexerResource->getTable('customer_group')], - 'gp.all_groups = 1 OR (gp.all_groups = 0 AND gp.customer_group_id = cg.customer_group_id)', - ['customer_group_id'] - )->join( - ['cw' => $this->_defaultIndexerResource->getTable('store_website')], - 'gp.website_id = 0 OR gp.website_id = cw.website_id', - ['website_id'] - )->join( - ['cwd' => $this->_defaultIndexerResource->getTable('catalog_product_index_website')], - 'cw.website_id = cwd.website_id', - [] - )->where( - 'cw.website_id != 0' - )->columns( - new \Zend_Db_Expr("MIN({$websiteExpression})") - )->group( - ['gp.entity_id', 'cg.customer_group_id', 'cw.website_id'] - ); - - if (!empty($entityIds)) { - $select->where('gp.entity_id IN(?)', $entityIds); - } - - $query = $select->insertFromSelect($table); - $this->_connection->query($query); - - return $this; - } - /** * Retrieve price indexers per product type * @@ -445,7 +396,6 @@ abstract class AbstractAction $this->_copyRelationIndexData($compositeIds, $notCompositeIds); } $this->_prepareTierPriceIndex($compositeIds + $notCompositeIds); - $this->_prepareGroupPriceIndex($compositeIds + $notCompositeIds); $indexers = $this->getTypeIndexers(); foreach ($indexers as $indexer) { diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php index ac9c4391efaa1bf23198ef8ec07c16436f5666eb..bb79da054188cd510bdfad42afd9c8989844da38 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php @@ -25,7 +25,6 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction $this->_emptyTable($this->_defaultIndexerResource->getIdxTable()); $this->_prepareWebsiteDateTable(); $this->_prepareTierPriceIndex(); - $this->_prepareGroupPriceIndex(); foreach ($this->getTypeIndexers() as $indexer) { $indexer->reindexAll(); diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index dd0f5ec4ead4f93ae5a12313ada595359471ef03..c571d1e0b4e1a67c5978d58489943524d53392be 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -12,7 +12,8 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Pricing\Object\SaleableInterface; use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface; -use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool; +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory; /** * Catalog product model @@ -28,6 +29,7 @@ use Magento\Framework\Api\Data\ImageContentInterface; * @method string getUrlKey() * @method Product setUrlKey(string $urlKey) * @method Product setRequestPath(string $requestPath) + * @method Product setWebsiteIds(array $ids) * * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.ExcessivePublicCount) @@ -281,11 +283,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ protected $productLinkExtensionFactory; - /** - * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory - */ - protected $mediaGalleryEntryFactory; - /** * @var \Magento\Framework\Api\DataObjectHelper */ @@ -314,7 +311,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements ProductInterface::STORE_ID, 'media_gallery', 'tier_price', - 'group_price', ]; /** @@ -322,6 +318,13 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ protected $joinProcessor; + /** + * Media converter pool + * + * @var Product\Attribute\Backend\Media\EntryConverterPool + */ + protected $mediaGalleryEntryConverterPool; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -350,11 +353,11 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements * @param Indexer\Product\Eav\Processor $productEavIndexerProcessor * @param CategoryRepositoryInterface $categoryRepository * @param Product\Image\CacheFactory $imageCacheFactory - * @param \Magento\Catalog\Model\ProductLink\CollectionProvider $entityCollectionProvider - * @param \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider + * @param ProductLink\CollectionProvider $entityCollectionProvider + * @param Product\LinkTypeProvider $linkTypeProvider * @param \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory * @param \Magento\Catalog\Api\Data\ProductLinkExtensionFactory $productLinkExtensionFactory - * @param \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory + * @param EntryConverterPool $mediaGalleryEntryConverterPool * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper * @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $joinProcessor * @param array $data @@ -393,7 +396,7 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider, \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory, \Magento\Catalog\Api\Data\ProductLinkExtensionFactory $productLinkExtensionFactory, - \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory, + EntryConverterPool $mediaGalleryEntryConverterPool, \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $joinProcessor, array $data = [] @@ -422,7 +425,7 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements $this->linkTypeProvider = $linkTypeProvider; $this->productLinkFactory = $productLinkFactory; $this->productLinkExtensionFactory = $productLinkExtensionFactory; - $this->mediaGalleryEntryFactory = $mediaGalleryEntryFactory; + $this->mediaGalleryEntryConverterPool = $mediaGalleryEntryConverterPool; $this->dataObjectHelper = $dataObjectHelper; $this->joinProcessor = $joinProcessor; parent::__construct( @@ -1059,38 +1062,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements return $this->_priceInfo; } - /** - * Get product group price for the customer - * - * @return float - */ - public function getGroupPrice() - { - return $this->getPriceModel()->getGroupPrice($this); - } - - /** - * Gets list of product group prices - * - * @return \Magento\Catalog\Api\Data\ProductGroupPriceInterface[]|null - */ - public function getGroupPrices() - { - return $this->getPriceModel()->getGroupPrices($this); - } - - /** - * Sets list of product group prices - * - * @param \Magento\Catalog\Api\Data\ProductGroupPriceInterface[] $groupPrices - * @return $this - */ - public function setGroupPrices(array $groupPrices = null) - { - $this->getPriceModel()->setGroupPrices($this, $groupPrices); - return $this; - } - /** * Gets list of product tier prices * @@ -2504,6 +2475,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ public function setTypeId($typeId) { + if ($typeId !== $this->_getData('type_id')) { + $this->_typeInstance = null; + } return $this->setData(self::TYPE_ID, $typeId); } @@ -2535,22 +2509,12 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ protected function convertToMediaGalleryInterface(array $mediaGallery) { - $productImages = $this->getMediaAttributeValues(); - $entries = []; foreach ($mediaGallery as $image) { - if (!isset($image['types'])) { - $image['types'] = array_keys($productImages, $image['file']); - } - $entry = $this->mediaGalleryEntryFactory->create(); - $this->dataObjectHelper->populateWithArray( - $entry, - $image, - '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface' - ); - if (isset($image['value_id'])) { - $entry->setId($image['value_id']); - } + $entry = $this + ->mediaGalleryEntryConverterPool + ->getConverterByMediaType($image['media_type']) + ->convertTo($this, $image); $entries[] = $entry; } return $entries; @@ -2570,45 +2534,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements return $convertedEntries; } - /** - * @param ImageContentInterface $content - * @return array - */ - protected function convertFromMediaGalleryEntryContentInterface( - ImageContentInterface $content = null - ) { - if ($content == null) { - return null; - } else { - return [ - 'data' => [ - ImageContentInterface::BASE64_ENCODED_DATA => $content->getBase64EncodedData(), - ImageContentInterface::TYPE => $content->getType(), - ImageContentInterface::NAME => $content->getName(), - ], - ]; - } - } - - /** - * @param ProductAttributeMediaGalleryEntryInterface $entry - * @return array - */ - protected function convertFromMediaGalleryInterface(ProductAttributeMediaGalleryEntryInterface $entry) - { - $entryArray = [ - "value_id" => $entry->getId(), - "file" => $entry->getFile(), - "label" => $entry->getLabel(), - "position" => $entry->getPosition(), - "disabled" => $entry->isDisabled(), - "types" => $entry->getTypes(), - "content" => $this->convertFromMediaGalleryEntryContentInterface($entry->getContent()), - ]; - - return $entryArray; - } - /** * @param ProductAttributeMediaGalleryEntryInterface[] $mediaGalleryEntries * @return $this @@ -2618,7 +2543,10 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements if ($mediaGalleryEntries !== null) { $images = []; foreach ($mediaGalleryEntries as $entry) { - $images[] = $this->convertFromMediaGalleryInterface($entry); + $images[] = $this + ->mediaGalleryEntryConverterPool + ->getConverterByMediaType($entry->getMediaType()) + ->convertFrom($entry); } $this->setData('media_gallery', ['images' => $images]); diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/AbstractMedia.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/AbstractMedia.php new file mode 100644 index 0000000000000000000000000000000000000000..c3f73eb63f00c660411d13e4c02788e4dd8a2758 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/AbstractMedia.php @@ -0,0 +1,487 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * Catalog product media gallery attribute backend model + * + * @author Magento Core Team <core@magentocommerce.com> + */ +namespace Magento\Catalog\Model\Product\Attribute\Backend; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem\DriverInterface; + +/** + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AbstractMedia extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend +{ + /** + * Resource model + * + * @var \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media + */ + protected $resourceModel; + + /** + * @var \Magento\Catalog\Model\Product\Media\Config + */ + protected $mediaConfig; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface + */ + protected $mediaDirectory; + + /** + * Json Helper + * + * @var \Magento\Framework\Json\Helper\Data + */ + protected $jsonHelper = null; + + /** + * Core file storage database + * + * @var \Magento\MediaStorage\Helper\File\Storage\Database + */ + protected $fileStorageDb = null; + + /** + * Core event manager proxy + * + * @var \Magento\Framework\Event\ManagerInterface + */ + protected $eventManager = null; + + /** + * Product factory + * + * @var \Magento\Catalog\Model\Resource\ProductFactory + */ + protected $productFactory; + + /** + * Construct + * + * @param \Magento\Catalog\Model\Resource\ProductFactory $productFactory + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb + * @param \Magento\Framework\Json\Helper\Data $jsonHelper + * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig + * @param \Magento\Framework\Filesystem $filesystem + * @param \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media $resourceProductAttribute + */ + public function __construct( + \Magento\Catalog\Model\Resource\ProductFactory $productFactory, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, + \Magento\Framework\Json\Helper\Data $jsonHelper, + \Magento\Catalog\Model\Product\Media\Config $mediaConfig, + \Magento\Framework\Filesystem $filesystem, + \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media $resourceProductAttribute + ) { + $this->productFactory = $productFactory; + $this->eventManager = $eventManager; + $this->fileStorageDb = $fileStorageDb; + $this->jsonHelper = $jsonHelper; + $this->resourceModel = $resourceProductAttribute; + $this->mediaConfig = $mediaConfig; + $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); + } + + /** + * Validate media_gallery attribute data + * + * @param \Magento\Catalog\Model\Product $object + * @return bool + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function validate($object) + { + if ($this->getAttribute()->getIsRequired()) { + $value = $object->getData($this->getAttribute()->getAttributeCode()); + if ($this->getAttribute()->isValueEmpty($value)) { + return false; + } + } + if ($this->getAttribute()->getIsUnique()) { + if (!$this->getAttribute()->getEntity()->checkAttributeUniqueValue($this->getAttribute(), $object)) { + $label = $this->getAttribute()->getFrontend()->getLabel(); + throw new LocalizedException(__('The value of attribute "%1" must be unique.', $label)); + } + } + + return true; + } + + /** + * Add image to media gallery and return new filename + * + * @param \Magento\Catalog\Model\Product $product + * @param string $file file path of image in file system + * @param string|string[] $mediaAttribute code of attribute with type 'media_image', + * leave blank if image should be only in gallery + * @param boolean $move if true, it will move source file + * @param boolean $exclude mark image as disabled in product page view + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function addImage( + \Magento\Catalog\Model\Product $product, + $file, + $mediaAttribute = null, + $move = false, + $exclude = true + ) { + $file = $this->mediaDirectory->getRelativePath($file); + if (!$this->mediaDirectory->isFile($file)) { + throw new LocalizedException(__('The image does not exist.')); + } + + $pathinfo = pathinfo($file); + $imgExtensions = ['jpg', 'jpeg', 'gif', 'png']; + if (!isset($pathinfo['extension']) || !in_array(strtolower($pathinfo['extension']), $imgExtensions)) { + throw new LocalizedException(__('Please correct the image file type.')); + } + + $fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($pathinfo['basename']); + $dispretionPath = \Magento\MediaStorage\Model\File\Uploader::getDispretionPath($fileName); + $fileName = $dispretionPath . '/' . $fileName; + + $fileName = $this->getNotDuplicatedFilename($fileName, $dispretionPath); + + $destinationFile = $this->mediaConfig->getTmpMediaPath($fileName); + + try { + /** @var $storageHelper \Magento\MediaStorage\Helper\File\Storage\Database */ + $storageHelper = $this->fileStorageDb; + if ($move) { + $this->mediaDirectory->renameFile($file, $destinationFile); + + //If this is used, filesystem should be configured properly + $storageHelper->saveFile($this->mediaConfig->getTmpMediaShortUrl($fileName)); + } else { + $this->mediaDirectory->copyFile($file, $destinationFile); + + $storageHelper->saveFile($this->mediaConfig->getTmpMediaShortUrl($fileName)); + $this->mediaDirectory->changePermissions($destinationFile, DriverInterface::WRITEABLE_FILE_MODE); + } + } catch (\Exception $e) { + throw new LocalizedException(__('We couldn\'t move this file: %1.', $e->getMessage())); + } + + $fileName = str_replace('\\', '/', $fileName); + + $attrCode = $this->getAttribute()->getAttributeCode(); + $mediaGalleryData = $product->getData($attrCode); + $position = 0; + if (!is_array($mediaGalleryData)) { + $mediaGalleryData = ['images' => []]; + } + + foreach ($mediaGalleryData['images'] as &$image) { + if (isset($image['position']) && $image['position'] > $position) { + $position = $image['position']; + } + } + + $position++; + $mediaGalleryData['images'][] = [ + 'file' => $fileName, + 'position' => $position, + 'label' => '', + 'disabled' => (int)$exclude, + ]; + + $product->setData($attrCode, $mediaGalleryData); + + if ($mediaAttribute !== null) { + $this->setMediaAttribute($product, $mediaAttribute, $fileName); + } + + return $fileName; + } + + /** + * Update image in gallery + * + * @param \Magento\Catalog\Model\Product $product + * @param string $file + * @param array $data + * @return $this + */ + public function updateImage(\Magento\Catalog\Model\Product $product, $file, $data) + { + $fieldsMap = [ + 'label' => 'label', + 'position' => 'position', + 'disabled' => 'disabled', + 'exclude' => 'disabled', + 'media_type' => 'media_type', + ]; + + $attrCode = $this->getAttribute()->getAttributeCode(); + + $mediaGalleryData = $product->getData($attrCode); + + if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { + return $this; + } + + foreach ($mediaGalleryData['images'] as &$image) { + if ($image['file'] == $file) { + foreach ($fieldsMap as $mappedField => $realField) { + if (isset($data[$mappedField])) { + $image[$realField] = $data[$mappedField]; + } + } + } + } + + $product->setData($attrCode, $mediaGalleryData); + return $this; + } + + /** + * Remove image from gallery + * + * @param \Magento\Catalog\Model\Product $product + * @param string $file + * @return $this + */ + public function removeImage(\Magento\Catalog\Model\Product $product, $file) + { + $attrCode = $this->getAttribute()->getAttributeCode(); + + $mediaGalleryData = $product->getData($attrCode); + + if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { + return $this; + } + + foreach ($mediaGalleryData['images'] as &$image) { + if ($image['file'] == $file) { + $image['removed'] = 1; + } + } + + $product->setData($attrCode, $mediaGalleryData); + + return $this; + } + + /** + * Retrieve image from gallery + * + * @param \Magento\Catalog\Model\Product $product + * @param string $file + * @return array|boolean + */ + public function getImage(\Magento\Catalog\Model\Product $product, $file) + { + $attrCode = $this->getAttribute()->getAttributeCode(); + $mediaGalleryData = $product->getData($attrCode); + if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { + return false; + } + + foreach ($mediaGalleryData['images'] as $image) { + if ($image['file'] == $file) { + return $image; + } + } + + return false; + } + + /** + * Clear media attribute value + * + * @param \Magento\Catalog\Model\Product $product + * @param string|string[] $mediaAttribute + * @return $this + */ + public function clearMediaAttribute(\Magento\Catalog\Model\Product $product, $mediaAttribute) + { + $mediaAttributeCodes = array_keys($product->getMediaAttributes()); + + if (is_array($mediaAttribute)) { + foreach ($mediaAttribute as $attribute) { + if (in_array($attribute, $mediaAttributeCodes)) { + $product->setData($attribute, null); + } + } + } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { + $product->setData($mediaAttribute, null); + } + + return $this; + } + + /** + * Set media attribute value + * + * @param \Magento\Catalog\Model\Product $product + * @param string|string[] $mediaAttribute + * @param string $value + * @return $this + */ + public function setMediaAttribute(\Magento\Catalog\Model\Product $product, $mediaAttribute, $value) + { + $mediaAttributeCodes = array_keys($product->getMediaAttributes()); + + if (is_array($mediaAttribute)) { + foreach ($mediaAttribute as $atttribute) { + if (in_array($atttribute, $mediaAttributeCodes)) { + $product->setData($atttribute, $value); + } + } + } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { + $product->setData($mediaAttribute, $value); + } + + return $this; + } + + /** + * Retrieve resource model + * + * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media + */ + protected function getResource() + { + return $this->resourceModel; + } + + /** + * @param string $file + * @return string + */ + protected function getFilenameFromTmp($file) + { + return strrpos($file, '.tmp') == strlen($file) - 4 ? substr($file, 0, strlen($file) - 4) : $file; + } + + /** + * Duplicate temporary images + * + * @param string $file + * @return string + */ + public function duplicateImageFromTmp($file) + { + $file = $this->getFilenameFromTmp($file); + + $destinationFile = $this->getUniqueFileName($file, true); + if ($this->fileStorageDb->checkDbUsage()) { + $this->fileStorageDb->copyFile( + $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaShortUrl($file)), + $this->mediaConfig->getTmpMediaShortUrl($destinationFile) + ); + } else { + $this->mediaDirectory->copyFile( + $this->mediaConfig->getTmpMediaPath($file), + $this->mediaConfig->getTmpMediaPath($destinationFile) + ); + } + return str_replace('\\', '/', $destinationFile); + } + + + /** + * Check whether file to move exists. Getting unique name + * + * @param string $file + * @param bool $forTmp + * @return string + */ + protected function getUniqueFileName($file, $forTmp = false) + { + if ($this->fileStorageDb->checkDbUsage()) { + $destFile = $this->fileStorageDb->getUniqueFilename( + $this->mediaConfig->getBaseMediaUrlAddition(), + $file + ); + } else { + $destinationFile = $forTmp + ? $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaPath($file)) + : $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaPath($file)); + $destFile = dirname( + $file + ) . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( + $destinationFile + ); + } + + return $destFile; + } + + /** + * Get filename which is not duplicated with other files in media temporary and media directories + * + * @param string $fileName + * @param string $dispretionPath + * @return string + */ + protected function getNotDuplicatedFilename($fileName, $dispretionPath) + { + $fileMediaName = $dispretionPath . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( + $this->mediaConfig->getMediaPath($fileName) + ); + $fileTmpMediaName = $dispretionPath . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( + $this->mediaConfig->getTmpMediaPath($fileName) + ); + + if ($fileMediaName != $fileTmpMediaName) { + if ($fileMediaName != $fileName) { + return $this->getNotDuplicatedFilename($fileMediaName, $dispretionPath); + } elseif ($fileTmpMediaName != $fileName) { + return $this->getNotDuplicatedFilename($fileTmpMediaName, $dispretionPath); + } + } + + return $fileMediaName; + } + + /** + * Retrieve data for update attribute + * + * @param \Magento\Catalog\Model\Product $object + * @return array + */ + public function getAffectedFields($object) + { + $data = []; + $images = (array)$object->getData($this->getAttribute()->getName()); + $tableName = $this->getResource()->getMainTable(); + foreach ($images['images'] as $value) { + if (empty($value['value_id'])) { + continue; + } + $data[$tableName][] = [ + 'attribute_id' => $this->getAttribute()->getAttributeId(), + 'value_id' => $value['value_id'], + 'entity_id' => $object->getId(), + ]; + } + return $data; + } + + /** + * Attribute value is not to be saved in a conventional way, separate table is used to store the complex value + * + * {@inheritdoc} + */ + public function isScalar() + { + return false; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice.php deleted file mode 100644 index 4631e7f7ac30c4aa6c334aff5e94e3c501f8837c..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Model\Product\Attribute\Backend; - -/** - * Catalog product group price backend attribute model - * - * @SuppressWarnings(PHPMD.LongVariable) - */ -class GroupPrice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice -{ - /** - * Catalog product attribute backend groupprice - * - * @var \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice - */ - protected $_productAttributeBackendGroupPrice; - - /** - * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Helper\Data $catalogData - * @param \Magento\Framework\App\Config\ScopeConfigInterface $config - * @param \Magento\Framework\Locale\FormatInterface $localeFormat - * @param \Magento\Catalog\Model\Product\Type $catalogProductType - * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement - * @param \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice $productAttributeBackendGroupPrice - */ - public function __construct( - \Magento\Directory\Model\CurrencyFactory $currencyFactory, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Helper\Data $catalogData, - \Magento\Framework\App\Config\ScopeConfigInterface $config, - \Magento\Framework\Locale\FormatInterface $localeFormat, - \Magento\Catalog\Model\Product\Type $catalogProductType, - \Magento\Customer\Api\GroupManagementInterface $groupManagement, - \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice $productAttributeBackendGroupPrice - ) { - $this->_productAttributeBackendGroupPrice = $productAttributeBackendGroupPrice; - parent::__construct( - $currencyFactory, - $storeManager, - $catalogData, - $config, - $localeFormat, - $catalogProductType, - $groupManagement - ); - } - - /** - * Retrieve resource instance - * - * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice - */ - protected function _getResource() - { - return $this->_productAttributeBackendGroupPrice; - } - - /** - * Error message when duplicates - * - * @return \Magento\Framework\Phrase - */ - protected function _getDuplicateErrorMessage() - { - return __('We found a duplicate website group price customer group.'); - } - - /** - * By default attribute value is considered non-scalar that can be stored in a generic way - * - * @return bool - */ - public function isScalar() - { - return false; - } -} diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php index 9d51374069419d6833c703aa1c1575e0580e57dc..86c2cf57d4b912e6909600696fe89dbdd1a2462f 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php @@ -107,7 +107,7 @@ abstract class AbstractGroupPrice extends Price /** * Retrieve resource instance * - * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice + * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Tierprice */ abstract protected function _getResource(); @@ -178,9 +178,9 @@ abstract class AbstractGroupPrice extends Price // if attribute scope is website and edit in store view scope // add global group prices for duplicates find if (!$attribute->isScopeGlobal() && $object->getStoreId()) { - $origGroupPrices = $object->getOrigData($attribute->getName()); - if ($origGroupPrices) { - foreach ($origGroupPrices as $price) { + $origPrices = $object->getOrigData($attribute->getName()); + if ($origPrices) { + foreach ($origPrices as $price) { if ($price['website_id'] == 0) { $compare = join( '-', @@ -312,11 +312,11 @@ abstract class AbstractGroupPrice extends Price $new = []; // prepare original data for compare - $origGroupPrices = $object->getOrigData($this->getAttribute()->getName()); - if (!is_array($origGroupPrices)) { - $origGroupPrices = []; + $origPrices = $object->getOrigData($this->getAttribute()->getName()); + if (!is_array($origPrices)) { + $origPrices = []; } - foreach ($origGroupPrices as $data) { + foreach ($origPrices as $data) { if ($data['website_id'] > 0 || $data['website_id'] == '0' && $isGlobal) { $key = join( '-', @@ -420,9 +420,9 @@ abstract class AbstractGroupPrice extends Price public function getAffectedFields($object) { $data = []; - $groupPrices = (array)$object->getData($this->getAttribute()->getName()); + $prices = (array)$object->getData($this->getAttribute()->getName()); $tableName = $this->_getResource()->getMainTable(); - foreach ($groupPrices as $value) { + foreach ($prices as $value) { $data[$tableName][] = [ 'attribute_id' => $this->getAttribute()->getAttributeId(), 'entity_id' => $object->getId(), @@ -436,7 +436,7 @@ abstract class AbstractGroupPrice extends Price /** * Get resource model instance * - * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice + * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice */ public function getResource() { diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media.php index b227df7cb1b8fb02bc22b5c19d922037900501fb..8ca5ffbc429e567ddc1b37f5c24b482e62ec5335 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media.php @@ -4,171 +4,48 @@ * See COPYING.txt for license details. */ -/** - * Catalog product media gallery attribute backend model - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Catalog\Model\Product\Attribute\Backend; -use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Catalog\Model\Product; +use Magento\MediaStorage\Model\File\Uploader as FileUploader; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Filesystem\DriverInterface; /** - * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * Class Media backend model */ -class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend +class Media extends Product\Attribute\Backend\AbstractMedia { /** - * @var array - */ - protected $_renamedImages = []; - - /** - * Resource model - * - * @var \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media - */ - protected $_resourceModel; - - /** - * @var \Magento\Catalog\Model\Product\Media\Config - */ - protected $_mediaConfig; - - /** - * @var \Magento\Framework\Filesystem\Directory\WriteInterface - */ - protected $_mediaDirectory; - - /** - * Json Helper - * - * @var \Magento\Framework\Json\Helper\Data - */ - protected $jsonHelper = null; - - /** - * Core file storage database - * - * @var \Magento\MediaStorage\Helper\File\Storage\Database - */ - protected $_fileStorageDb = null; - - /** - * Core event manager proxy - * - * @var \Magento\Framework\Event\ManagerInterface - */ - protected $_eventManager = null; - - /** - * Product factory - * - * @var \Magento\Catalog\Model\Resource\ProductFactory - */ - protected $_productFactory; - - /** - * Construct - * - * @param \Magento\Catalog\Model\Resource\ProductFactory $productFactory - * @param \Magento\Framework\Event\ManagerInterface $eventManager - * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb - * @param \Magento\Framework\Json\Helper\Data $jsonHelper - * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig - * @param \Magento\Framework\Filesystem $filesystem - * @param \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media $resourceProductAttribute - */ - public function __construct( - \Magento\Catalog\Model\Resource\ProductFactory $productFactory, - \Magento\Framework\Event\ManagerInterface $eventManager, - \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, - \Magento\Framework\Json\Helper\Data $jsonHelper, - \Magento\Catalog\Model\Product\Media\Config $mediaConfig, - \Magento\Framework\Filesystem $filesystem, - \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media $resourceProductAttribute - ) { - $this->_productFactory = $productFactory; - $this->_eventManager = $eventManager; - $this->_fileStorageDb = $fileStorageDb; - $this->jsonHelper = $jsonHelper; - $this->_resourceModel = $resourceProductAttribute; - $this->_mediaConfig = $mediaConfig; - $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); - } - - /** - * Load attribute data after product loaded - * - * @param \Magento\Catalog\Model\Product $object - * @return \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend + * @param Product $object + * @return Product */ public function afterLoad($object) { - $attrCode = $this->getAttribute()->getAttributeCode(); + $attribute = $this->getAttribute(); + $attrCode = $attribute->getAttributeCode(); $value = []; $value['images'] = []; $value['values'] = []; $localAttributes = ['label', 'position', 'disabled']; - foreach ($this->_getResource()->loadGallery($object, $this) as $image) { + $mediaEntries = $this->getResource()->loadProductGalleryByAttributeId($object, $attribute->getId()); + foreach ($mediaEntries as $mediaEntry) { foreach ($localAttributes as $localAttribute) { - if ($image[$localAttribute] === null) { - $image[$localAttribute] = $this->_getDefaultValue($localAttribute, $image); + if ($mediaEntry[$localAttribute] === null) { + $mediaEntry[$localAttribute] = $this->findDefaultValue($localAttribute, $mediaEntry); } } - $value['images'][] = $image; + $value['images'][] = $mediaEntry; } - $object->setData($attrCode, $value); - return $this; - } - - /** - * @param string $key - * @param string[] &$image - * @return string - */ - protected function _getDefaultValue($key, &$image) - { - if (isset($image[$key . '_default'])) { - return $image[$key . '_default']; - } - - return ''; - } - /** - * Validate media_gallery attribute data - * - * @param \Magento\Catalog\Model\Product $object - * @return bool - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function validate($object) - { - if ($this->getAttribute()->getIsRequired()) { - $value = $object->getData($this->getAttribute()->getAttributeCode()); - if ($this->getAttribute()->isValueEmpty($value)) { - return false; - } - } - if ($this->getAttribute()->getIsUnique()) { - if (!$this->getAttribute()->getEntity()->checkAttributeUniqueValue($this->getAttribute(), $object)) { - $label = $this->getAttribute()->getFrontend()->getLabel(); - throw new LocalizedException(__('The value of attribute "%1" must be unique.', $label)); - } - } - - return true; + return $object; } /** - * @param \Magento\Framework\DataObject $object - * @return $this|void + * @param Product $object + * @return Product * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -177,7 +54,7 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend $attrCode = $this->getAttribute()->getAttributeCode(); $value = $object->getData($attrCode); if (!is_array($value) || !isset($value['images'])) { - return; + return $object; } if (!is_array($value['images']) && strlen($value['images']) > 0) { @@ -196,10 +73,9 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend if (!empty($image['removed'])) { $clearImages[] = $image['file']; } elseif (empty($image['value_id'])) { - $newFile = $this->_moveImageFromTmp($image['file']); + $newFile = $this->moveImageFromTmp($image['file']); $image['new_file'] = $newFile; $newImages[$image['file']] = $image; - $this->_renamedImages[$image['file']] = $newFile; $image['file'] = $newFile; } else { $existImages[$image['file']] = $image; @@ -212,7 +88,7 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend if (empty($image['value_id'])) { continue; } - $duplicate[$image['value_id']] = $this->_copyImage($image['file']); + $duplicate[$image['value_id']] = $this->copyImage($image['file']); $image['new_file'] = $duplicate[$image['value_id']]; $newImages[$image['file']] = $image; } @@ -240,41 +116,26 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend $object->setData($attrCode, $value); - return $this; - } - - /** - * Retrieve renamed image name - * - * @param string $file - * @return string - */ - public function getRenamedImage($file) - { - if (isset($this->_renamedImages[$file])) { - return $this->_renamedImages[$file]; - } - - return $file; + return $object; } /** - * @param \Magento\Framework\DataObject $object - * @return void + * @param Product $object + * @return Product * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function afterSave($object) { if ($object->getIsDuplicate() == true) { - $this->duplicate($object); - return; + $this->duplicate($object, $this->getAttribute()); + return $object; } $attrCode = $this->getAttribute()->getAttributeCode(); $value = $object->getData($attrCode); if (!is_array($value) || !isset($value['images']) || $object->isLockedAttribute($attrCode)) { - return; + return $object; } $storeId = $object->getStoreId(); @@ -287,7 +148,7 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend unset($storeIds[$storeId]); $storeIds = array_keys($storeIds); - $images = $this->_productFactory->create()->getAssignedImages($object, $storeIds); + $images = $this->productFactory->create()->getAssignedImages($object, $storeIds); $picturesInOtherStores = []; foreach ($images as $image) { @@ -307,17 +168,25 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend if (empty($image['value_id'])) { $data = []; - $data['entity_id'] = $object->getId(); $data['attribute_id'] = $this->getAttribute()->getId(); $data['value'] = $image['file']; - $image['value_id'] = $this->_getResource()->insertGallery($data); + if (!empty($image['media_type'])) { + $data['media_type'] = $image['media_type']; + } + $image['value_id'] = $this->getResource()->insertGallery($data); + $this->getResource()->bindValueToEntity($image['value_id'], $object->getId()); } - $this->_getResource()->deleteGalleryValueInStore($image['value_id'], $object->getStoreId()); + $this->getResource()->deleteGalleryValueInStore( + $image['value_id'], + $object->getId(), + $object->getStoreId() + ); // Add per store labels, position, disabled $data = []; $data['value_id'] = $image['value_id']; + $data['entity_id'] = $object->getId(); $data['label'] = isset($image['label']) ? $image['label'] : ''; $data['position'] = isset($image['position']) ? (int)$image['position'] : 0; @@ -325,312 +194,49 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend $data['store_id'] = (int)$object->getStoreId(); $data['entity_id'] = (int)$object->getId(); - $this->_getResource()->insertGalleryValueInStore($data); + $this->getResource()->insertGalleryValueInStore($data); } - $this->_getResource()->deleteGallery($recordsToDelete); + $this->getResource()->deleteGallery($recordsToDelete); $this->removeDeletedImages($filesToDelete); $object->setData($attrCode, $value); - } - - /** - * @param array $files - * @return null - */ - protected function removeDeletedImages(array $files) - { - $catalogPath = $this->_mediaConfig->getBaseMediaPath(); - foreach ($files as $filePath) { - $this->_mediaDirectory->delete($catalogPath . '/' . $filePath); - } - } - - /** - * Add image to media gallery and return new filename - * - * @param \Magento\Catalog\Model\Product $product - * @param string $file file path of image in file system - * @param string|string[] $mediaAttribute code of attribute with type 'media_image', - * leave blank if image should be only in gallery - * @param boolean $move if true, it will move source file - * @param boolean $exclude mark image as disabled in product page view - * @return string - * @throws \Magento\Framework\Exception\LocalizedException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function addImage( - \Magento\Catalog\Model\Product $product, - $file, - $mediaAttribute = null, - $move = false, - $exclude = true - ) { - $file = $this->_mediaDirectory->getRelativePath($file); - if (!$this->_mediaDirectory->isFile($file)) { - throw new LocalizedException(__('The image does not exist.')); - } - - $pathinfo = pathinfo($file); - $imgExtensions = ['jpg', 'jpeg', 'gif', 'png']; - if (!isset($pathinfo['extension']) || !in_array(strtolower($pathinfo['extension']), $imgExtensions)) { - throw new LocalizedException(__('Please correct the image file type.')); - } - - $fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($pathinfo['basename']); - $dispretionPath = \Magento\MediaStorage\Model\File\Uploader::getDispretionPath($fileName); - $fileName = $dispretionPath . '/' . $fileName; - - $fileName = $this->_getNotDuplicatedFilename($fileName, $dispretionPath); - - $destinationFile = $this->_mediaConfig->getTmpMediaPath($fileName); - - try { - /** @var $storageHelper \Magento\MediaStorage\Helper\File\Storage\Database */ - $storageHelper = $this->_fileStorageDb; - if ($move) { - $this->_mediaDirectory->renameFile($file, $destinationFile); - - //If this is used, filesystem should be configured properly - $storageHelper->saveFile($this->_mediaConfig->getTmpMediaShortUrl($fileName)); - } else { - $this->_mediaDirectory->copyFile($file, $destinationFile); - - $storageHelper->saveFile($this->_mediaConfig->getTmpMediaShortUrl($fileName)); - $this->_mediaDirectory->changePermissions($destinationFile, DriverInterface::WRITEABLE_FILE_MODE); - } - } catch (\Exception $e) { - throw new LocalizedException(__('We couldn\'t move this file: %1.', $e->getMessage())); - } - - $fileName = str_replace('\\', '/', $fileName); - - $attrCode = $this->getAttribute()->getAttributeCode(); - $mediaGalleryData = $product->getData($attrCode); - $position = 0; - if (!is_array($mediaGalleryData)) { - $mediaGalleryData = ['images' => []]; - } - - foreach ($mediaGalleryData['images'] as &$image) { - if (isset($image['position']) && $image['position'] > $position) { - $position = $image['position']; - } - } - - $position++; - $mediaGalleryData['images'][] = [ - 'file' => $fileName, - 'position' => $position, - 'label' => '', - 'disabled' => (int)$exclude, - ]; - - $product->setData($attrCode, $mediaGalleryData); - - if ($mediaAttribute !== null) { - $this->setMediaAttribute($product, $mediaAttribute, $fileName); - } - - return $fileName; - } - - /** - * Add images with different media attributes. - * Image will be added only once if the same image is used with different media attributes - * - * @param \Magento\Catalog\Model\Product $product - * @param array $fileAndAttributesArray array of arrays of filename and corresponding media attribute - * @param string $filePath path, where image cand be found - * @param boolean $move if true, it will move source file - * @param boolean $exclude mark image as disabled in product page view - * @return array array of parallel arrays with original and renamed files - */ - public function addImagesWithDifferentMediaAttributes( - \Magento\Catalog\Model\Product $product, - $fileAndAttributesArray, - $filePath = '', - $move = false, - $exclude = true - ) { - $alreadyAddedFiles = []; - $alreadyAddedFilesNames = []; - - foreach ($fileAndAttributesArray as $key => $value) { - $keyInAddedFiles = array_search($value['file'], $alreadyAddedFiles, true); - if ($keyInAddedFiles === false) { - $savedFileName = $this->addImage($product, $filePath . $value['file'], null, $move, $exclude); - $alreadyAddedFiles[$key] = $value['file']; - $alreadyAddedFilesNames[$key] = $savedFileName; - } else { - $savedFileName = $alreadyAddedFilesNames[$keyInAddedFiles]; - } - if ($value['mediaAttribute'] !== null) { - $this->setMediaAttribute($product, $value['mediaAttribute'], $savedFileName); - } - } - - return ['alreadyAddedFiles' => $alreadyAddedFiles, 'alreadyAddedFilesNames' => $alreadyAddedFilesNames]; + return $object; } /** - * Update image in gallery - * * @param \Magento\Catalog\Model\Product $product - * @param string $file - * @param array $data + * @param AbstractAttribute $attribute * @return $this */ - public function updateImage(\Magento\Catalog\Model\Product $product, $file, $data) + protected function duplicate($product, $attribute) { - $fieldsMap = [ - 'label' => 'label', - 'position' => 'position', - 'disabled' => 'disabled', - 'exclude' => 'disabled', - ]; - - $attrCode = $this->getAttribute()->getAttributeCode(); - - $mediaGalleryData = $product->getData($attrCode); + $mediaGalleryData = $product->getData($attribute->getAttributeCode()); if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { return $this; } - foreach ($mediaGalleryData['images'] as &$image) { - if ($image['file'] == $file) { - foreach ($fieldsMap as $mappedField => $realField) { - if (isset($data[$mappedField])) { - $image[$realField] = $data[$mappedField]; - } - } - } - } - - $product->setData($attrCode, $mediaGalleryData); - return $this; - } - - /** - * Remove image from gallery - * - * @param \Magento\Catalog\Model\Product $product - * @param string $file - * @return $this - */ - public function removeImage(\Magento\Catalog\Model\Product $product, $file) - { - $attrCode = $this->getAttribute()->getAttributeCode(); - - $mediaGalleryData = $product->getData($attrCode); - - if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { - return $this; - } - - foreach ($mediaGalleryData['images'] as &$image) { - if ($image['file'] == $file) { - $image['removed'] = 1; - } - } - - $product->setData($attrCode, $mediaGalleryData); - - return $this; - } - - /** - * Retrieve image from gallery - * - * @param \Magento\Catalog\Model\Product $product - * @param string $file - * @return array|boolean - */ - public function getImage(\Magento\Catalog\Model\Product $product, $file) - { - $attrCode = $this->getAttribute()->getAttributeCode(); - $mediaGalleryData = $product->getData($attrCode); - if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { - return false; - } - - foreach ($mediaGalleryData['images'] as $image) { - if ($image['file'] == $file) { - return $image; - } - } - - return false; - } - - /** - * Clear media attribute value - * - * @param \Magento\Catalog\Model\Product $product - * @param string|string[] $mediaAttribute - * @return $this - */ - public function clearMediaAttribute(\Magento\Catalog\Model\Product $product, $mediaAttribute) - { - $mediaAttributeCodes = array_keys($product->getMediaAttributes()); - - if (is_array($mediaAttribute)) { - foreach ($mediaAttribute as $atttribute) { - if (in_array($atttribute, $mediaAttributeCodes)) { - $product->setData($atttribute, null); - } - } - } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { - $product->setData($mediaAttribute, null); - } + $this->getResource()->duplicate( + $attribute->getId(), + isset($mediaGalleryData['duplicate']) ? $mediaGalleryData['duplicate'] : [], + $product->getOriginalId(), + $product->getId() + ); return $this; } /** - * Set media attribute value - * - * @param \Magento\Catalog\Model\Product $product - * @param string|string[] $mediaAttribute - * @param string $value - * @return $this + * @param array $files + * @return null */ - public function setMediaAttribute(\Magento\Catalog\Model\Product $product, $mediaAttribute, $value) + protected function removeDeletedImages(array $files) { - $mediaAttributeCodes = array_keys($product->getMediaAttributes()); - - if (is_array($mediaAttribute)) { - foreach ($mediaAttribute as $atttribute) { - if (in_array($atttribute, $mediaAttributeCodes)) { - $product->setData($atttribute, $value); - } - } - } elseif (in_array($mediaAttribute, $mediaAttributeCodes)) { - $product->setData($mediaAttribute, $value); + $catalogPath = $this->mediaConfig->getBaseMediaPath(); + foreach ($files as $filePath) { + $this->mediaDirectory->delete($catalogPath . '/' . $filePath); } - - return $this; - } - - /** - * Retrieve resource model - * - * @return \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media - */ - protected function _getResource() - { - return $this->_resourceModel; - } - - /** - * @param string $file - * @return string - */ - protected function getFilenameFromTmp($file) - { - return strrpos($file, '.tmp') == strlen($file) - 4 ? substr($file, 0, strlen($file) - 4) : $file; } /** @@ -639,26 +245,23 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend * @param string $file * @return string */ - protected function _moveImageFromTmp($file) + protected function moveImageFromTmp($file) { $file = $this->getFilenameFromTmp($file); - $destinationFile = $this->_getUniqueFileName($file); + $destinationFile = $this->getUniqueFileName($file); - /** @var $storageHelper \Magento\MediaStorage\Helper\File\Storage\Database */ - $storageHelper = $this->_fileStorageDb; - - if ($storageHelper->checkDbUsage()) { - $storageHelper->renameFile( - $this->_mediaConfig->getTmpMediaShortUrl($file), - $this->_mediaConfig->getMediaShortUrl($destinationFile) + if ($this->fileStorageDb->checkDbUsage()) { + $this->fileStorageDb->renameFile( + $this->mediaConfig->getTmpMediaShortUrl($file), + $this->mediaConfig->getMediaShortUrl($destinationFile) ); - $this->_mediaDirectory->delete($this->_mediaConfig->getTmpMediaPath($file)); - $this->_mediaDirectory->delete($this->_mediaConfig->getMediaPath($destinationFile)); + $this->mediaDirectory->delete($this->mediaConfig->getTmpMediaPath($file)); + $this->mediaDirectory->delete($this->mediaConfig->getMediaPath($destinationFile)); } else { - $this->_mediaDirectory->renameFile( - $this->_mediaConfig->getTmpMediaPath($file), - $this->_mediaConfig->getMediaPath($destinationFile) + $this->mediaDirectory->renameFile( + $this->mediaConfig->getTmpMediaPath($file), + $this->mediaConfig->getMediaPath($destinationFile) ); } @@ -666,57 +269,37 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend } /** - * Duplicate temporary images + * Check whether file to move exists. Getting unique name * * @param string $file + * @param bool $forTmp * @return string */ - public function duplicateImageFromTmp($file) + protected function getUniqueFileName($file, $forTmp = false) { - $file = $this->getFilenameFromTmp($file); - - $destinationFile = $this->_getUniqueFileName($file, true); - if ($this->_fileStorageDb->checkDbUsage()) { - $this->_fileStorageDb->copyFile( - $this->_mediaDirectory->getAbsolutePath($this->_mediaConfig->getTmpMediaShortUrl($file)), - $this->_mediaConfig->getTmpMediaShortUrl($destinationFile) + if ($this->fileStorageDb->checkDbUsage()) { + $destFile = $this->fileStorageDb->getUniqueFilename( + $this->mediaConfig->getBaseMediaUrlAddition(), + $file ); } else { - $this->_mediaDirectory->copyFile( - $this->_mediaConfig->getTmpMediaPath($file), - $this->_mediaConfig->getTmpMediaPath($destinationFile) - ); + $destinationFile = $forTmp + ? $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaPath($file)) + : $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaPath($file)); + $destFile = dirname($file) . '/' . FileUploader::getNewFileName($destinationFile); } - return str_replace('\\', '/', $destinationFile); + + return $destFile; } /** - * Check whether file to move exists. Getting unique name - * * @param string $file - * @param bool $forTmp * @return string */ - protected function _getUniqueFileName($file, $forTmp = false) + protected function getFilenameFromTmp($file) { - if ($this->_fileStorageDb->checkDbUsage()) { - $destFile = $this->_fileStorageDb->getUniqueFilename( - $this->_mediaConfig->getBaseMediaUrlAddition(), - $file - ); - } else { - $destinationFile = $forTmp - ? $this->_mediaDirectory->getAbsolutePath($this->_mediaConfig->getTmpMediaPath($file)) - : $this->_mediaDirectory->getAbsolutePath($this->_mediaConfig->getMediaPath($file)); - $destFile = dirname( - $file - ) . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( - $destinationFile - ); - } - - return $destFile; + return strrpos($file, '.tmp') == strlen($file) - 4 ? substr($file, 0, strlen($file) - 4) : $file; } /** @@ -726,31 +309,31 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend * @return string * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _copyImage($file) + protected function copyImage($file) { try { - $destinationFile = $this->_getUniqueFileName($file); + $destinationFile = $this->getUniqueFileName($file); - if (!$this->_mediaDirectory->isFile($this->_mediaConfig->getMediaPath($file))) { + if (!$this->mediaDirectory->isFile($this->mediaConfig->getMediaPath($file))) { throw new \Exception(); } - if ($this->_fileStorageDb->checkDbUsage()) { - $this->_fileStorageDb->copyFile( - $this->_mediaDirectory->getAbsolutePath($this->_mediaConfig->getMediaShortUrl($file)), - $this->_mediaConfig->getMediaShortUrl($destinationFile) + if ($this->fileStorageDb->checkDbUsage()) { + $this->fileStorageDb->copyFile( + $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaShortUrl($file)), + $this->mediaConfig->getMediaShortUrl($destinationFile) ); - $this->_mediaDirectory->delete($this->_mediaConfig->getMediaPath($destinationFile)); + $this->mediaDirectory->delete($this->mediaConfig->getMediaPath($destinationFile)); } else { - $this->_mediaDirectory->copyFile( - $this->_mediaConfig->getMediaPath($file), - $this->_mediaConfig->getMediaPath($destinationFile) + $this->mediaDirectory->copyFile( + $this->mediaConfig->getMediaPath($file), + $this->mediaConfig->getMediaPath($destinationFile) ); } return str_replace('\\', '/', $destinationFile); } catch (\Exception $e) { - $file = $this->_mediaConfig->getMediaPath($file); + $file = $this->mediaConfig->getMediaPath($file); throw new LocalizedException( __('We couldn\'t copy file %1. Please delete media with non-existing images and try again.', $file) ); @@ -758,86 +341,16 @@ class Media extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend } /** - * @param \Magento\Framework\DataObject $object - * @return $this - */ - public function duplicate($object) - { - $attrCode = $this->getAttribute()->getAttributeCode(); - $mediaGalleryData = $object->getData($attrCode); - - if (!isset($mediaGalleryData['images']) || !is_array($mediaGalleryData['images'])) { - return $this; - } - - $this->_getResource()->duplicate( - $this, - isset($mediaGalleryData['duplicate']) ? $mediaGalleryData['duplicate'] : [], - $object->getOriginalId(), - $object->getId() - ); - - return $this; - } - - /** - * Get filename which is not duplicated with other files in media temporary and media directories - * - * @param string $fileName - * @param string $dispretionPath + * @param string $key + * @param string[] &$image * @return string */ - protected function _getNotDuplicatedFilename($fileName, $dispretionPath) + protected function findDefaultValue($key, &$image) { - $fileMediaName = $dispretionPath . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( - $this->_mediaConfig->getMediaPath($fileName) - ); - $fileTmpMediaName = $dispretionPath . '/' . \Magento\MediaStorage\Model\File\Uploader::getNewFileName( - $this->_mediaConfig->getTmpMediaPath($fileName) - ); - - if ($fileMediaName != $fileTmpMediaName) { - if ($fileMediaName != $fileName) { - return $this->_getNotDuplicatedFileName($fileMediaName, $dispretionPath); - } elseif ($fileTmpMediaName != $fileName) { - return $this->_getNotDuplicatedFilename($fileTmpMediaName, $dispretionPath); - } - } - - return $fileMediaName; - } - - /** - * Retrieve data for update attribute - * - * @param \Magento\Catalog\Model\Product $object - * @return array - */ - public function getAffectedFields($object) - { - $data = []; - $images = (array)$object->getData($this->getAttribute()->getName()); - $tableName = $this->_getResource()->getMainTable(); - foreach ($images['images'] as $value) { - if (empty($value['value_id'])) { - continue; - } - $data[$tableName][] = [ - 'attribute_id' => $this->getAttribute()->getAttributeId(), - 'value_id' => $value['value_id'], - 'entity_id' => $object->getId(), - ]; + if (isset($image[$key . '_default'])) { + return $image[$key . '_default']; } - return $data; - } - /** - * Attribute value is not to be saved in a conventional way, separate table is used to store the complex value - * - * {@inheritdoc} - */ - public function isScalar() - { - return false; + return ''; } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..d6da504890f44e2c1526f1d9ccc41651e590b38e --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterInterface.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Attribute\Backend\Media; + +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface; +use Magento\Catalog\Model\Product; + +/** + * Interface EntryConverterInterface. Create Media Gallery Entry and extract Entry data + */ +interface EntryConverterInterface +{ + /** + * Return Media Gallery Entry type + * + * @return string + */ + public function getMediaEntryType(); + + /** + * Create Media Gallery Entry entity from a row input data + * + * @param Product $product + * @param array $rowData + * @return ProductAttributeMediaGalleryEntryInterface[] + */ + public function convertTo(Product $product, array $rowData); + + /** + * Convert given Media Gallery Entry to raw data collection + * + * @param ProductAttributeMediaGalleryEntryInterface $entry + * @return array + */ + public function convertFrom(ProductAttributeMediaGalleryEntryInterface $entry); +} diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterPool.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterPool.php new file mode 100644 index 0000000000000000000000000000000000000000..205803c7b71b493f37b101e9446aef596676cb80 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/EntryConverterPool.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Attribute\Backend\Media; + +/** + * Class aggregate all Media Gallery Entry Converters + */ +class EntryConverterPool +{ + /** + * @var EntryConverterInterface[] + */ + private $mediaGalleryEntryConvertersCollection; + + /** + * @param EntryConverterInterface[] $mediaGalleryEntryConvertersCollection + */ + public function __construct(array $mediaGalleryEntryConvertersCollection) + { + foreach ($mediaGalleryEntryConvertersCollection as $converter) { + if (!$converter instanceof EntryConverterInterface) { + throw new \InvalidArgumentException( + __('Media Gallery converter should be an instance of EntryConverterInterface.') + ); + } + } + $this->mediaGalleryEntryConvertersCollection = $mediaGalleryEntryConvertersCollection; + } + + /** + * Get specific converter by given media entry type + * + * @param string $mediaType + * @return EntryConverterInterface + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getConverterByMediaType($mediaType) + { + foreach ($this->mediaGalleryEntryConvertersCollection as $converter) { + if ($converter->getMediaEntryType() == $mediaType) { + return $converter; + } + } + throw new \Magento\Framework\Exception\LocalizedException( + __('There is no MediaGalleryEntryConverter for given type') + ); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/ImageEntryConverter.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/ImageEntryConverter.php new file mode 100644 index 0000000000000000000000000000000000000000..d179b21cf9cb52432f713f71500408759e89de78 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Media/ImageEntryConverter.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Attribute\Backend\Media; + +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface; +use Magento\Catalog\Model\Product; +use Magento\Framework\Api\Data\ImageContentInterface; + +/** + * Converter for Image media gallery type + */ +class ImageEntryConverter implements EntryConverterInterface +{ + /** + * Media Entry type code + */ + const MEDIA_TYPE_CODE = 'image'; + + /** + * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory + */ + protected $mediaGalleryEntryFactory; + + /** + * @var \Magento\Framework\Api\DataObjectHelper + */ + protected $dataObjectHelper; + + /** + * @param \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory + * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + */ + public function __construct( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory, + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + ) { + $this->mediaGalleryEntryFactory = $mediaGalleryEntryFactory; + $this->dataObjectHelper = $dataObjectHelper; + } + + /** + * {@inheritdoc} + */ + public function getMediaEntryType() + { + return self::MEDIA_TYPE_CODE; + } + + /** + * @param Product $product + * @param array $rowData + * @return ProductAttributeMediaGalleryEntryInterface $entry + */ + public function convertTo(Product $product, array $rowData) + { + $image = $rowData; + $productImages = $product->getMediaAttributeValues(); + if (!isset($image['types'])) { + $image['types'] = array_keys($productImages, $image['file']); + } + $entry = $this->mediaGalleryEntryFactory->create(); + $this->dataObjectHelper->populateWithArray( + $entry, + $image, + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface' + ); + if (isset($image['value_id'])) { + $entry->setId($image['value_id']); + } + return $entry; + } + + /** + * @param ProductAttributeMediaGalleryEntryInterface $entry + * @return array + */ + public function convertFrom(ProductAttributeMediaGalleryEntryInterface $entry) + { + $entryArray = [ + 'value_id' => $entry->getId(), + 'file' => $entry->getFile(), + 'label' => $entry->getLabel(), + 'position' => $entry->getPosition(), + 'disabled' => $entry->isDisabled(), + 'types' => $entry->getTypes(), + 'media_type' => $entry->getMediaType(), + 'content' => $this->convertFromMediaGalleryEntryContentInterface($entry->getContent()), + ]; + return $entryArray; + } + + /** + * @param ImageContentInterface $content + * @return array + */ + protected function convertFromMediaGalleryEntryContentInterface( + ImageContentInterface $content = null + ) { + if ($content == null) { + return null; + } else { + return [ + 'data' => [ + ImageContentInterface::BASE64_ENCODED_DATA => $content->getBase64EncodedData(), + ImageContentInterface::TYPE => $content->getType(), + ImageContentInterface::NAME => $content->getName(), + ], + ]; + } + } +} diff --git a/app/code/Magento/Catalog/Model/Product/AttributeSet/SuggestedSet.php b/app/code/Magento/Catalog/Model/Product/AttributeSet/SuggestedSet.php new file mode 100644 index 0000000000000000000000000000000000000000..0957c87b34b27d9d4baf2f811ab7111e78446124 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/AttributeSet/SuggestedSet.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Product\AttributeSet; + +/** + * Suggested product attribute set + */ +class SuggestedSet +{ + /** + * Set collection factory + * + * @var \Magento\Eav\Model\Resource\Entity\Attribute\Set\CollectionFactory + */ + protected $attributeSetCollectionFactory; + + /** + * Catalog resource helper + * + * @var \Magento\Catalog\Model\Resource\Helper + */ + protected $resourceHelper; + + /** + * @var \Magento\Catalog\Model\Resource\Product + */ + protected $product; + + /** + * @param \Magento\Catalog\Model\Resource\Product $product + * @param \Magento\Eav\Model\Resource\Entity\Attribute\Set\CollectionFactory $attributeSetCollectionFactory + * @param \Magento\Catalog\Model\Resource\Helper $resourceHelper + */ + public function __construct( + \Magento\Catalog\Model\Resource\Product $product, + \Magento\Eav\Model\Resource\Entity\Attribute\Set\CollectionFactory $attributeSetCollectionFactory, + \Magento\Catalog\Model\Resource\Helper $resourceHelper + ) { + $this->attributeSetCollectionFactory = $attributeSetCollectionFactory; + $this->resourceHelper = $resourceHelper; + $this->product = $product; + } + + /** + * Retrieve list of product attribute sets with search part contained in label + * + * @param string $labelPart + * @return array + */ + public function getSuggestedSets($labelPart) + { + $labelPart = $this->resourceHelper->addLikeEscape($labelPart, ['position' => 'any']); + /** @var \Magento\Eav\Model\Resource\Entity\Attribute\Set\Collection $collection */ + $collection = $this->attributeSetCollectionFactory->create(); + $collection->setEntityTypeFilter( + $this->product->getTypeId() + )->addFieldToFilter( + 'attribute_set_name', + ['like' => $labelPart] + )->addFieldToSelect( + 'attribute_set_id', + 'id' + )->addFieldToSelect( + 'attribute_set_name', + 'label' + )->setOrder( + 'attribute_set_name', + \Magento\Eav\Model\Resource\Entity\Attribute\Set\Collection::SORT_ORDER_ASC + ); + return $collection->getData(); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Edit/WeightResolver.php b/app/code/Magento/Catalog/Model/Product/Edit/WeightResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..074ee3a7503e1d19713da63d4cf6283a1e9aa258 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Edit/WeightResolver.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Edit; + +class WeightResolver +{ + /** + * Product has weight + */ + const HAS_WEIGHT = 1; + + /** + * Product don't have weight + */ + const HAS_NO_WEIGHT = 0; + + /** + * @param \Magento\Catalog\Model\Product $product + * @return bool + */ + public function resolveProductHasWeight(\Magento\Catalog\Model\Product $product) + { + return (bool) ($product->getData('product_has_weight') == self::HAS_WEIGHT); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php b/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php index daa709f33ca13ffbd717fb77d6eda8c425755c5e..2d43d7f86bc2083ddd2c247bea4e44078296798f 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/Entry.php @@ -25,6 +25,16 @@ class Entry extends AbstractExtensibleModel implements ProductAttributeMediaGall return $this->getData(self::ID); } + /** + * Get media type + * + * @return string + */ + public function getMediaType() + { + return $this->getData(self::MEDIA_TYPE); + } + /** * Retrieve gallery entry alternative text * @@ -83,6 +93,17 @@ class Entry extends AbstractExtensibleModel implements ProductAttributeMediaGall return $this->getData(self::CONTENT); } + /** + * Set media type + * + * @param string $mediaType + * @return $this + */ + public function setMediaType($mediaType) + { + return $this->setData(self::MEDIA_TYPE, $mediaType); + } + /** * Set gallery entry alternative text * diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php index e99cf1fa36900f328d57b685c2bf72ef81e88e70..1d3b5fb26b1ffcb3a6dc2b86d83fa792c888b8c5 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php @@ -177,7 +177,7 @@ class GalleryManagement implements \Magento\Catalog\Api\ProductAttributeMediaGal /** * {@inheritdoc} */ - public function get($sku, $imageId) + public function get($sku, $entryId) { try { $product = $this->productRepository->get($sku); @@ -187,7 +187,7 @@ class GalleryManagement implements \Magento\Catalog\Api\ProductAttributeMediaGal $mediaGalleryEntries = $product->getMediaGalleryEntries(); foreach ($mediaGalleryEntries as $entry) { - if ($entry->getId() == $imageId) { + if ($entry->getId() == $entryId) { return $entry; } } diff --git a/app/code/Magento/Catalog/Model/Product/GroupPrice.php b/app/code/Magento/Catalog/Model/Product/GroupPrice.php deleted file mode 100644 index 740a2b1b976b47d16b07166bb7551220cf779dbf..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Model/Product/GroupPrice.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Model\Product; - -/** - * @codeCoverageIgnore - */ -class GroupPrice extends \Magento\Framework\Model\AbstractExtensibleModel implements - \Magento\Catalog\Api\Data\ProductGroupPriceInterface -{ - /**#@+ - * Constants - */ - const KEY_CUSTOMER_GROUP_ID = 'customer_group_id'; - const KEY_VALUE = 'value'; - /**#@-*/ - - /** - * Retrieve customer group id - * - * @return int - */ - public function getCustomerGroupId() - { - return $this->getData(self::KEY_CUSTOMER_GROUP_ID); - } - - /** - * Retrieve price value - * - * @return float - */ - public function getValue() - { - return $this->getData(self::KEY_VALUE); - } - - /** - * Set customer group id - * - * @param int $customerGroupId - * @return $this - */ - public function setCustomerGroupId($customerGroupId) - { - return $this->setData(self::KEY_CUSTOMER_GROUP_ID, $customerGroupId); - } - - /** - * Set price value - * - * @param float $value - * @return $this - */ - public function setValue($value) - { - return $this->setData(self::KEY_VALUE, $value); - } - - /** - * {@inheritdoc} - * - * @return \Magento\Catalog\Api\Data\ProductGroupPriceExtensionInterface|null - */ - public function getExtensionAttributes() - { - return $this->_getExtensionAttributes(); - } - - /** - * {@inheritdoc} - * - * @param \Magento\Catalog\Api\Data\ProductGroupPriceExtensionInterface $extensionAttributes - * @return $this - */ - public function setExtensionAttributes( - \Magento\Catalog\Api\Data\ProductGroupPriceExtensionInterface $extensionAttributes - ) { - return $this->_setExtensionAttributes($extensionAttributes); - } -} diff --git a/app/code/Magento/Catalog/Model/Product/GroupPriceManagement.php b/app/code/Magento/Catalog/Model/Product/GroupPriceManagement.php deleted file mode 100644 index 9dca54ee7ebd52fc62098ff3ab77c67f21eac864..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Model/Product/GroupPriceManagement.php +++ /dev/null @@ -1,159 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Model\Product; - -use Magento\Customer\Api\GroupRepositoryInterface; -use Magento\Framework\Exception\CouldNotSaveException; -use Magento\Framework\Exception\InputException; - -class GroupPriceManagement implements \Magento\Catalog\Api\ProductGroupPriceManagementInterface -{ - /** - * @var \Magento\Catalog\Api\ProductRepositoryInterface - */ - protected $productRepository; - - /** - * @var \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory - */ - protected $groupPriceFactory; - - /** - * @var GroupRepositoryInterface - */ - protected $groupRepository; - - /** - * @var \Magento\Catalog\Model\Product\PriceModifier - */ - protected $priceModifier; - - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ - protected $config; - - /** - * @var \Magento\Store\Model\StoreManagerInterface - */ - protected $storeManager; - - /** - * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory $groupPriceFactory - * @param GroupRepositoryInterface $groupRepository - * @param PriceModifier $priceModifier - * @param \Magento\Framework\App\Config\ScopeConfigInterface $config - */ - public function __construct( - \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, - \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory $groupPriceFactory, - GroupRepositoryInterface $groupRepository, - \Magento\Catalog\Model\Product\PriceModifier $priceModifier, - \Magento\Framework\App\Config\ScopeConfigInterface $config, - \Magento\Store\Model\StoreManagerInterface $storeManager - ) { - $this->productRepository = $productRepository; - $this->groupPriceFactory = $groupPriceFactory; - $this->groupRepository = $groupRepository; - $this->priceModifier = $priceModifier; - $this->config = $config; - $this->storeManager = $storeManager; - } - - /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function add($sku, $customerGroupId, $price) - { - if (!\Zend_Validate::is($price, 'Float') || $price <= 0 || !\Zend_Validate::is($price, 'Float')) { - throw new InputException(__('Please provide valid data')); - } - $customerGroup = $this->groupRepository->getById($customerGroupId); - $product = $this->productRepository->get($sku, true); - $groupPrices = $product->getData('group_price'); - $websiteIdentifier = 0; - $value = $this->config->getValue('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE); - if ($value != 0) { - $websiteIdentifier = $this->storeManager->getWebsite()->getId(); - } - $found = false; - foreach ($groupPrices as &$currentPrice) { - if (intval($currentPrice['cust_group']) === $customerGroupId - && intval($currentPrice['website_id']) === intval($websiteIdentifier) - ) { - $currentPrice['price'] = $price; - $found = true; - break; - } - } - if (!$found) { - $groupPrices[] = [ - 'cust_group' => $customerGroup->getId(), - 'website_id' => $websiteIdentifier, - 'price' => $price, - ]; - } - - $product->setData('group_price', $groupPrices); - $errors = $product->validate(); - if (is_array($errors) && count($errors)) { - $errorAttributeCodes = implode(', ', array_keys($errors)); - throw new InputException( - __('Values of following attributes are invalid: %1', $errorAttributeCodes) - ); - } - try { - $this->productRepository->save($product); - } catch (\Exception $e) { - throw new CouldNotSaveException(__('Could not save group price')); - } - return true; - } - - /** - * {@inheritdoc} - */ - public function remove($sku, $customerGroupId) - { - $product = $this->productRepository->get($sku, true); - $websiteIdentifier = 0; - $value = $this->config->getValue('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE); - if ($value != 0) { - $websiteIdentifier = $this->storeManager->getWebsite()->getId(); - } - $this->priceModifier->removeGroupPrice($product, $customerGroupId, $websiteIdentifier); - return true; - } - - /** - * {@inheritdoc} - */ - public function getList($sku, $websiteId = null) - { - $product = $this->productRepository->get($sku, true); - $priceKey = 'website_price'; - $value = $this->config->getValue('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE); - if ($value == 0) { - $priceKey = 'price'; - } - - $prices = []; - foreach ($product->getData('group_price') as $price) { - /** @var \Magento\Catalog\Api\Data\ProductGroupPriceInterface $groupPrice */ - $groupPrice = $this->groupPriceFactory->create(); - $groupPrice->setCustomerGroupId($price['all_groups'] ? 'all' : $price['cust_group']) - ->setValue($price[$priceKey]); - $prices[] = $groupPrice; - } - return $prices; - } -} diff --git a/app/code/Magento/Catalog/Model/Product/Link.php b/app/code/Magento/Catalog/Model/Product/Link.php index 7ca13f75821ec5872e76e0292ad92105f18f3cb8..a088df34cf7dfe0b6c87487b6516425b939cddb5 100644 --- a/app/code/Magento/Catalog/Model/Product/Link.php +++ b/app/code/Magento/Catalog/Model/Product/Link.php @@ -49,11 +49,17 @@ class Link extends \Magento\Framework\Model\AbstractModel */ protected $_linkCollectionFactory; + /** + * @var \Magento\CatalogInventory\Helper\Stock + */ + protected $stockHelper; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Catalog\Model\Resource\Product\Link\CollectionFactory $linkCollectionFactory * @param \Magento\Catalog\Model\Resource\Product\Link\Product\CollectionFactory $productCollectionFactory + * @param \Magento\CatalogInventory\Helper\Stock $stockHelper * @param \Magento\Framework\Model\Resource\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data @@ -63,12 +69,14 @@ class Link extends \Magento\Framework\Model\AbstractModel \Magento\Framework\Registry $registry, \Magento\Catalog\Model\Resource\Product\Link\CollectionFactory $linkCollectionFactory, \Magento\Catalog\Model\Resource\Product\Link\Product\CollectionFactory $productCollectionFactory, + \Magento\CatalogInventory\Helper\Stock $stockHelper, \Magento\Framework\Model\Resource\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [] ) { $this->_linkCollectionFactory = $linkCollectionFactory; $this->_productCollectionFactory = $productCollectionFactory; + $this->stockHelper = $stockHelper; parent::__construct($context, $registry, $resource, $resourceCollection, $data); } @@ -128,6 +136,7 @@ class Link extends \Magento\Framework\Model\AbstractModel public function getProductCollection() { $collection = $this->_productCollectionFactory->create()->setLinkModel($this); + $this->stockHelper->addInStockFilterToCollection($collection); return $collection; } diff --git a/app/code/Magento/Catalog/Model/Product/PriceModifier.php b/app/code/Magento/Catalog/Model/Product/PriceModifier.php index 4197e2c6f5a8657fa03b9217d4c13b30b4a6b661..83ec7a6c95a8fe14fbf527731dffca667df3fad1 100644 --- a/app/code/Magento/Catalog/Model/Product/PriceModifier.php +++ b/app/code/Magento/Catalog/Model/Product/PriceModifier.php @@ -25,45 +25,6 @@ class PriceModifier $this->productRepository = $productRepository; } - /** - * @param \Magento\Catalog\Model\Product $product - * @param int $customerGroupId - * @param int $websiteId - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @return void - */ - public function removeGroupPrice(\Magento\Catalog\Model\Product $product, $customerGroupId, $websiteId) - { - $prices = $product->getData('group_price'); - if ($prices === null) { - throw new NoSuchEntityException(__('This product doesn\'t have group price')); - } - $groupPriceQty = count($prices); - - foreach ($prices as $key => $groupPrice) { - if ($groupPrice['cust_group'] == $customerGroupId - && intval($groupPrice['website_id']) === intval($websiteId) - ) { - unset($prices[$key]); - } - } - if ($groupPriceQty == count($prices)) { - throw new NoSuchEntityException( - __( - 'Product hasn\'t group price with such data: customerGroupId = \'%1\', website = %2.', - [$customerGroupId, $websiteId] - ) - ); - } - $product->setData('group_price', $prices); - try { - $this->productRepository->save($product); - } catch (\Exception $exception) { - throw new CouldNotSaveException(__('Invalid data provided for group price')); - } - } - /** * @param \Magento\Catalog\Model\Product $product * @param int|string $customerGroupId diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 10414b85e006a28e23ea850b9fcfe5e622245925..3ae0481599236e88586287f9b43f36f4f3a265d1 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -70,11 +70,6 @@ class Price */ protected $_groupManagement; - /** - * @var \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory - */ - protected $groupPriceFactory; - /** * @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory */ @@ -86,6 +81,7 @@ class Price protected $config; /** + * Price constructor. * @param \Magento\CatalogRule\Model\Resource\RuleFactory $ruleFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate @@ -93,7 +89,6 @@ class Price * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param PriceCurrencyInterface $priceCurrency * @param GroupManagementInterface $groupManagement - * @param \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory $groupPriceFactory * @param \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @@ -107,7 +102,6 @@ class Price \Magento\Framework\Event\ManagerInterface $eventManager, PriceCurrencyInterface $priceCurrency, GroupManagementInterface $groupManagement, - \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory $groupPriceFactory, \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory, \Magento\Framework\App\Config\ScopeConfigInterface $config ) { @@ -118,7 +112,6 @@ class Price $this->_eventManager = $eventManager; $this->priceCurrency = $priceCurrency; $this->_groupManagement = $groupManagement; - $this->groupPriceFactory = $groupPriceFactory; $this->tierPriceFactory = $tierPriceFactory; $this->config = $config; } @@ -146,7 +139,6 @@ class Price { $price = (float) $product->getPrice(); return min( - $this->_applyGroupPrice($product, $price), $this->_applyTierPrice($product, $qty, $price), $this->_applySpecialPrice($product, $price) ); @@ -184,6 +176,7 @@ class Price * @param Product $childProduct * @param float $childProductQty * @return float + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getChildFinalPrice($product, $productQty, $childProduct, $childProductQty) @@ -192,48 +185,7 @@ class Price } /** - * Apply group price for product - * - * @param Product $product - * @param float $finalPrice - * @return float - */ - protected function _applyGroupPrice($product, $finalPrice) - { - $groupPrice = $product->getGroupPrice(); - if (is_numeric($groupPrice)) { - $finalPrice = min($finalPrice, $groupPrice); - } - return $finalPrice; - } - - /** - * Gets list of product group prices - * - * @param Product $product - * @return \Magento\Catalog\Api\Data\ProductGroupPriceInterface[] - */ - public function getGroupPrices($product) - { - $prices = []; - $groupPrices = $this->getExistingPrices($product, 'group_price'); - foreach ($groupPrices as $price) { - /** @var \Magento\Catalog\Api\Data\ProductGroupPriceInterface $groupPrice */ - $groupPrice = $this->groupPriceFactory->create(); - $groupPrice->setCustomerGroupId($price['cust_group']); - if (array_key_exists('website_price', $price)) { - $value = $price['website_price']; - } else { - $value = $price['price']; - } - $groupPrice->setValue($value); - $prices[] = $groupPrice; - } - return $prices; - } - - /** - * Gets the 'group_price' array from the product + * Gets the 'tear_price' array from the product * * @param Product $product * @param string $key @@ -259,39 +211,6 @@ class Price return $prices; } - /** - * Sets list of product group prices - * - * @param Product $product - * @param \Magento\Catalog\Api\Data\ProductGroupPriceInterface[] $groupPrices - * @return $this - */ - public function setGroupPrices($product, array $groupPrices = null) - { - // null array means leave everything as is - if ($groupPrices === null) { - return $this; - } - - $websiteId = $this->getWebsiteForPriceScope(); - $allGroupsId = $this->getAllCustomerGroupsId(); - - // build the new array of group prices - $prices = []; - foreach ($groupPrices as $price) { - $prices[] = [ - 'website_id' => $websiteId, - 'cust_group' => $price->getCustomerGroupId(), - 'website_price' => $price->getValue(), - 'price' => $price->getValue(), - 'all_groups' => ($price->getCustomerGroupId() == $allGroupsId) - ]; - } - $product->setData('group_price', $prices); - - return $this; - } - /** * Returns the website to use for group or tier prices, based on the price scope setting * @@ -308,35 +227,6 @@ class Price return $websiteId; } - /** - * Get product group price for the customer - * - * @param Product $product - * @return float - * @deprecated see \Magento\Catalog\Pricing\Price\GroupPrice (MAGETWO-31468) - */ - public function getGroupPrice($product) - { - $groupPrices = $this->getGroupPrices($product); - - if (empty($groupPrices)) { - return $product->getPrice(); - } - - $customerGroup = $this->_getCustomerGroupId($product); - - $matchedPrice = $product->getPrice(); - foreach ($groupPrices as $groupPrice) { - /** @var \Magento\Catalog\Api\Data\ProductGroupPriceInterface $groupPrice */ - if ($groupPrice->getCustomerGroupId() == $customerGroup && $groupPrice->getValue() < $matchedPrice) { - $matchedPrice = $groupPrice->getValue(); - break; - } - } - - return $matchedPrice; - } - /** * Apply tier price for product if not return price that was before * @@ -707,16 +597,6 @@ class Price * @return bool */ public function isTierPriceFixed() - { - return $this->isGroupPriceFixed(); - } - - /** - * Check is group price value fixed or percent of original price - * - * @return bool - */ - public function isGroupPriceFixed() { return true; } diff --git a/app/code/Magento/Catalog/Model/Product/TypeTransitionManager.php b/app/code/Magento/Catalog/Model/Product/TypeTransitionManager.php index 99dd6a233fa65cb5ef2e531d09d8bb742ec938d9..077de91f00e23d30d9111e766fcb3ec47085824c 100644 --- a/app/code/Magento/Catalog/Model/Product/TypeTransitionManager.php +++ b/app/code/Magento/Catalog/Model/Product/TypeTransitionManager.php @@ -11,6 +11,7 @@ namespace Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Type; class TypeTransitionManager { @@ -22,11 +23,20 @@ class TypeTransitionManager protected $compatibleTypes; /** + * @var Edit\WeightResolver + */ + protected $weightResolver; + + /** + * @param Edit\WeightResolver $weightResolver * @param array $compatibleTypes */ - public function __construct(array $compatibleTypes) - { + public function __construct( + \Magento\Catalog\Model\Product\Edit\WeightResolver $weightResolver, + array $compatibleTypes + ) { $this->compatibleTypes = $compatibleTypes; + $this->weightResolver = $weightResolver; } /** @@ -39,7 +49,9 @@ class TypeTransitionManager { if (in_array($product->getTypeId(), $this->compatibleTypes)) { $product->setTypeInstance(null); - $productTypeId = $product->hasIsVirtual() ? \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL : \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE; + $productTypeId = $this->weightResolver->resolveProductHasWeight($product) + ? Type::TYPE_SIMPLE + : Type::TYPE_VIRTUAL; $product->setTypeId($productTypeId); } } diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index 2a6ddbd8e9e15933f34c71cdcb9b588235176e0c..4f7d539901ddfdb72b36eaf3b37c44cf018c36d3 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -17,69 +17,53 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; class Url extends \Magento\Framework\DataObject { /** - * Static URL instance + * URL instance * - * @var \Magento\Framework\UrlInterface + * @var \Magento\Framework\UrlFactory */ - protected $_url; + protected $urlFactory; /** * @var \Magento\Framework\Filter\FilterManager */ protected $filter; - /** - * Catalog category - * - * @var \Magento\Catalog\Helper\Category - */ - protected $_catalogCategory = null; - /** * Store manager * * @var \Magento\Store\Model\StoreManagerInterface */ - protected $_storeManager; + protected $storeManager; /** * @var \Magento\Framework\Session\SidResolverInterface */ - protected $_sidResolver; - - /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator */ - protected $productUrlPathGenerator; + protected $sidResolver; /** @var UrlFinderInterface */ protected $urlFinder; /** - * @param \Magento\Framework\UrlInterface $url + * @param \Magento\Framework\UrlFactory $urlFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Helper\Category $catalogCategory * @param \Magento\Framework\Filter\FilterManager $filter * @param \Magento\Framework\Session\SidResolverInterface $sidResolver - * @param \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator * @param UrlFinderInterface $urlFinder * @param array $data */ public function __construct( - \Magento\Framework\UrlInterface $url, + \Magento\Framework\UrlFactory $urlFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Helper\Category $catalogCategory, \Magento\Framework\Filter\FilterManager $filter, \Magento\Framework\Session\SidResolverInterface $sidResolver, - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator $productUrlPathGenerator, UrlFinderInterface $urlFinder, array $data = [] ) { parent::__construct($data); - $this->_url = $url; - $this->_storeManager = $storeManager; - $this->_catalogCategory = $catalogCategory; + $this->urlFactory = $urlFactory; + $this->storeManager = $storeManager; $this->filter = $filter; - $this->_sidResolver = $sidResolver; - $this->productUrlPathGenerator = $productUrlPathGenerator; + $this->sidResolver = $sidResolver; $this->urlFinder = $urlFinder; } @@ -88,23 +72,9 @@ class Url extends \Magento\Framework\DataObject * * @return \Magento\Framework\UrlInterface */ - public function getUrlInstance() - { - return $this->_url; - } - - /** - * 'no_selection' shouldn't be a valid image attribute value - * - * @param string $image - * @return string - */ - protected function _validImage($image) + private function getUrlInstance() { - if ($image == 'no_selection') { - $image = null; - } - return $image; + return $this->urlFactory->create(); } /** @@ -130,7 +100,7 @@ class Url extends \Magento\Framework\DataObject public function getProductUrl($product, $useSid = null) { if ($useSid === null) { - $useSid = $this->_sidResolver->getUseSessionInUrl(); + $useSid = $this->sidResolver->getUseSessionInUrl(); } $params = []; @@ -167,12 +137,11 @@ class Url extends \Magento\Framework\DataObject $routeParams = $params; $storeId = $product->getStoreId(); - if (isset($params['_ignore_category'])) { - unset($params['_ignore_category']); - $categoryId = null; - } else { - $categoryId = $product->getCategoryId() && - !$product->getDoNotUseCategoryId() ? $product->getCategoryId() : null; + + $categoryId = null; + + if (!isset($params['_ignore_category']) && $product->getCategoryId() && !$product->getDoNotUseCategoryId()) { + $categoryId = $product->getCategoryId(); } if ($product->hasUrlDataObject()) { @@ -200,10 +169,10 @@ class Url extends \Magento\Framework\DataObject } if (isset($routeParams['_scope'])) { - $storeId = $this->_storeManager->getStore($routeParams['_scope'])->getId(); + $storeId = $this->storeManager->getStore($routeParams['_scope'])->getId(); } - if ($storeId != $this->_storeManager->getStore()->getId()) { + if ($storeId != $this->storeManager->getStore()->getId()) { $routeParams['_scope_to_url'] = true; } diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index aa8a3027a9d37115bd1382bc47ddd93d9a33f913..4355e93eddc1b3b7a19e11e049090d88a53d3485 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -467,6 +467,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa 'label' => $newEntry['label'], 'position' => $newEntry['position'], 'disabled' => $newEntry['disabled'], + 'media_type' => $newEntry['media_type'], ] ); return $this; @@ -546,7 +547,6 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $productOptions = $product->getProductOptions(); } $isDeleteOptions = $product->getIsDeleteOptions(); - $groupPrices = $product->getData('group_price'); $tierPrices = $product->getData('tier_price'); $productId = $this->resourceModel->getIdBySku($product->getSku()); @@ -584,9 +584,6 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $product->setProductOptions($productOptions); $product->setCanSaveCustomOptions(true); } - if ($groupPrices !== null) { - $product->setData('group_price', $groupPrices); - } if ($tierPrices !== null) { $product->setData('tier_price', $tierPrices); } diff --git a/app/code/Magento/Catalog/Model/Resource/Eav/Attribute.php b/app/code/Magento/Catalog/Model/Resource/Eav/Attribute.php index 5b14ccbcc4d8db7f43959e7a5d04c27413166585..27d8fa9b6477c0b5ad3195c0382e23d83b594334 100644 --- a/app/code/Magento/Catalog/Model/Resource/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/Resource/Eav/Attribute.php @@ -20,9 +20,9 @@ use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface; * @method \Magento\Catalog\Model\Resource\Eav\Attribute getFrontendInputRenderer() * @method string setFrontendInputRenderer(string $value) * @method int setIsGlobal(int $value) - * @method \Magento\Catalog\Model\Resource\Eav\Attribute getSearchWeight() + * @method int getSearchWeight() * @method int setSearchWeight(int $value) - * @method \Magento\Catalog\Model\Resource\Eav\Attribute getIsUsedForPriceRules() + * @method bool getIsUsedForPriceRules() * @method int setIsUsedForPriceRules(int $value) * @method \Magento\Eav\Api\Data\AttributeExtensionInterface getExtensionAttributes() * diff --git a/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/GroupPrice.php b/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/GroupPrice.php deleted file mode 100644 index a7dc53782db16cfcc9aec6acbe9503111fac6aa7..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/GroupPrice.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Model\Resource\Product\Attribute\Backend; - -use Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice; - -/** - * Catalog product group price backend attribute model - * - * @author Magento Core Team <core@magentocommerce.com> - */ -class GroupPrice extends AbstractGroupPrice -{ - /** - * Initialize connection and define main table - * - * @return void - */ - protected function _construct() - { - $this->_init('catalog_product_entity_group_price', 'value_id'); - } -} diff --git a/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/Media.php b/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/Media.php index c17a4bf881bbd1a3b8f843ab85ae1d690eff8b35..81054c233bc50d88bf65f9b14cfa3108b550f57c 100644 --- a/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/Media.php +++ b/app/code/Magento/Catalog/Model/Resource/Product/Attribute/Backend/Media.php @@ -8,6 +8,8 @@ namespace Magento\Catalog\Model\Resource\Product\Attribute\Backend; +use Magento\Catalog\Model\Product; + /** * Catalog product media gallery attribute backend resource * @@ -19,6 +21,8 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb const GALLERY_VALUE_TABLE = 'catalog_product_entity_media_gallery_value'; + const GALLERY_VALUE_TO_ENTITY_TABLE = 'catalog_product_entity_media_gallery_value_to_entity'; + /** * Resource initialization * @@ -30,13 +34,105 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb } /** - * Load gallery images for product - * - * @param \Magento\Catalog\Model\Product $product - * @param \Magento\Catalog\Model\Product\Attribute\Backend\Media $object + * @param Product $product + * @param int $attributeId * @return array */ - public function loadGallery($product, $object) + public function loadProductGalleryByAttributeId($product, $attributeId) + { + $select = $this->createBaseLoadSelect($product->getId(), $product->getStoreId(), $attributeId); + $result = $this->getConnection()->fetchAll($select); + $this->_removeDuplicates($result); + return $result; + } + + /** + * @param string $tableNameAlias + * @param array $ids + * @param int|null $storeId + * @param array|null $cols + * @param array $leftJoinTables + * @param string $whereCondition + * @return array + */ + public function loadDataFromTableByValueId( + $tableNameAlias, + array $ids, + $storeId = null, + array $cols = null, + array $leftJoinTables = [], + $whereCondition = null + ) { + if (null == $cols) { + $cols = '*'; + } + $connection = $this->getConnection(); + $mainTableAlias = $this->getMainTableAlias(); + $select = $connection->select() + ->from( + [$mainTableAlias => $this->getTable($tableNameAlias)], $cols + )->where( + $mainTableAlias . '.value_id IN(?)', + $ids + ); + if (null !== $storeId) { + $select->where($mainTableAlias . '.store_id = ?', $storeId); + } + if (null !== $whereCondition) { + $select->where($whereCondition); + } + foreach ($leftJoinTables as $joinParameters) { + $select->joinLeft($joinParameters[0], $joinParameters[1], $joinParameters[2]); + } + $result = $this->getConnection()->fetchAll($select); + + return $result; + } + + /** + * @param int $valueId + * @param int $entityId + * @return int + */ + public function bindValueToEntity($valueId, $entityId) + { + return $this->saveDataRow( + self::GALLERY_VALUE_TO_ENTITY_TABLE, + [ + 'value_id' => $valueId, + 'entity_id' => $entityId + ] + ); + } + + /** + * @param string $table + * @param array $data + * @param array $fields + * @return int + */ + public function saveDataRow($table, array $data, array $fields = []) + { + $table = $this->getTable($table); + return $this->getConnection()->insertOnDuplicate($table, $data, $fields); + } + + /** + * @return string + */ + public function getMainTableAlias() + { + return 'main'; + } + + /** + * @param int $entityId + * @param int $storeId + * @param int $attributeId + * @return \Magento\Framework\DB\Select + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function createBaseLoadSelect($entityId, $storeId, $attributeId) { $connection = $this->getConnection(); @@ -46,39 +142,52 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb 'value.position' ); - // Select gallery images for product + $mainTableAlias = $this->getMainTableAlias(); $select = $connection->select()->from( - ['main' => $this->getMainTable()], + [$mainTableAlias => $this->getMainTable()], [ 'value_id', - 'file' => 'value' + 'file' => 'value', + 'media_type' => 'media_type' ] + )->joinInner( + ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], + $mainTableAlias . '.value_id = entity.value_id', + ['entity_id' => 'entity_id'] )->joinLeft( ['value' => $this->getTable(self::GALLERY_VALUE_TABLE)], - $connection->quoteInto('main.value_id = value.value_id AND value.store_id = ?', (int)$product->getStoreId()), - [ - 'label', - 'position', - 'disabled' - ] + implode( + ' AND ', + [ + $mainTableAlias . '.value_id = value.value_id', + $connection->quoteInto('value.store_id = ?', (int)$storeId), + $connection->quoteInto('value.entity_id = ?', (int)$entityId) + ] + ), + ['label', 'position', 'disabled'] )->joinLeft( - // Joining default values ['default_value' => $this->getTable(self::GALLERY_VALUE_TABLE)], - 'main.value_id = default_value.value_id AND default_value.store_id = 0', + implode( + ' AND ', + [ + $mainTableAlias . '.value_id = default_value.value_id', + 'default_value.store_id = 0', + $connection->quoteInto('default_value.entity_id = ?', (int)$entityId) + ] + ), ['label_default' => 'label', 'position_default' => 'position', 'disabled_default' => 'disabled'] )->where( - 'main.attribute_id = ?', - $object->getAttribute()->getId() + $mainTableAlias . '.attribute_id = ?', + $attributeId )->where( - 'main.entity_id = ?', - $product->getId() + $mainTableAlias . '.disabled = 0' + )->where( + 'entity.entity_id = ?', + $entityId ) - ->where($positionCheckSql . ' IS NOT NULL') - ->order($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC); + ->order($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC); - $result = $connection->fetchAll($select); - $this->_removeDuplicates($result); - return $result; + return $select; } /** @@ -147,7 +256,10 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb */ public function insertGalleryValueInStore($data) { - $data = $this->_prepareDataForTable(new \Magento\Framework\DataObject($data), $this->getTable(self::GALLERY_VALUE_TABLE)); + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject($data), + $this->getTable(self::GALLERY_VALUE_TABLE) + ); $this->getConnection()->insert($this->getTable(self::GALLERY_VALUE_TABLE), $data); return $this; @@ -156,11 +268,12 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb /** * Delete gallery value for store in db * - * @param integer $valueId - * @param integer $storeId + * @param int $valueId + * @param int $entityId + * @param int $storeId * @return $this */ - public function deleteGalleryValueInStore($valueId, $storeId) + public function deleteGalleryValueInStore($valueId, $entityId, $storeId) { $connection = $this->getConnection(); @@ -168,6 +281,7 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb ' AND ', [ $connection->quoteInto('value_id = ?', (int)$valueId), + $connection->quoteInto('entity_id = ?', (int)$entityId), $connection->quoteInto('store_id = ?', (int)$storeId) ] ); @@ -180,22 +294,28 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb /** * Duplicates gallery db values * - * @param \Magento\Catalog\Model\Product\Attribute\Backend\Media $object + * @param int $attributeId * @param array $newFiles * @param int $originalProductId * @param int $newProductId * @return $this */ - public function duplicate($object, $newFiles, $originalProductId, $newProductId) + public function duplicate($attributeId, $newFiles, $originalProductId, $newProductId) { + $mainTableAlias = $this->getMainTableAlias(); + $select = $this->getConnection()->select()->from( - $this->getMainTable(), + [$mainTableAlias => $this->getMainTable()], ['value_id', 'value'] + )->joinInner( + ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], + $mainTableAlias . '.value_id = entity.value_id', + ['entity_id' => 'entity_id'] )->where( 'attribute_id = ?', - $object->getAttribute()->getId() + $attributeId )->where( - 'entity_id = ?', + 'entity.entity_id = ?', $originalProductId ); @@ -203,7 +323,7 @@ class Media extends \Magento\Framework\Model\Resource\Db\AbstractDb // Duplicate main entries of gallery foreach ($this->getConnection()->fetchAll($select) as $row) { $data = [ - 'attribute_id' => $object->getAttribute()->getId(), + 'attribute_id' => $attributeId, 'entity_id' => $newProductId, 'value' => isset($newFiles[$row['value_id']]) ? $newFiles[$row['value_id']] : $row['value'], ]; diff --git a/app/code/Magento/Catalog/Model/Resource/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/Resource/Product/Indexer/Price/DefaultPrice.php index e1ff4568ae04fdec31ce2787b0f91c8d36cee751..7c7ddf75369102654cb39e7b45b2e23eb7a00211 100644 --- a/app/code/Magento/Catalog/Model/Resource/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/Resource/Product/Indexer/Price/DefaultPrice.php @@ -258,11 +258,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' . ' AND tp.customer_group_id = cg.customer_group_id', [] - )->joinLeft( - ['gp' => $this->_getGroupPriceIndexTable()], - 'gp.entity_id = e.entity_id AND gp.website_id = cw.website_id' . - ' AND gp.customer_group_id = cg.customer_group_id', - [] )->where( 'e.type_id = ?', $this->getTypeId() @@ -286,7 +281,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr $specialFrom = $this->_addAttributeToSelect($select, 'special_from_date', 'e.entity_id', 'cs.store_id'); $specialTo = $this->_addAttributeToSelect($select, 'special_to_date', 'e.entity_id', 'cs.store_id'); $currentDate = $connection->getDatePartSql('cwd.website_date'); - $groupPrice = $connection->getCheckSql('gp.price IS NULL', "{$price}", 'gp.price'); $specialFromDate = $connection->getDatePartSql($specialFrom); $specialToDate = $connection->getDatePartSql($specialTo); @@ -300,7 +294,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr $specialPrice, $price ); - $finalPrice = $connection->getCheckSql("{$groupPrice} < {$finalPrice}", $groupPrice, $finalPrice); $select->columns( [ @@ -310,8 +303,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr 'max_price' => $finalPrice, 'tier_price' => new \Zend_Db_Expr('tp.min_price'), 'base_tier' => new \Zend_Db_Expr('tp.min_price'), - 'group_price' => new \Zend_Db_Expr('gp.price'), - 'base_group_price' => new \Zend_Db_Expr('gp.price'), ] ); @@ -442,12 +433,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr $tierPriceValue = $connection->getCheckSql("MIN(o.is_require) > 0", $tierPriceMin, 0); $tierPrice = $connection->getCheckSql("MIN(i.base_tier) IS NOT NULL", $tierPriceValue, "NULL"); - $groupPriceRound = new \Zend_Db_Expr("ROUND(i.base_group_price * ({$optPriceValue} / 100), 4)"); - $groupPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $groupPriceRound); - $groupPriceMin = new \Zend_Db_Expr("MIN({$groupPriceExpr})"); - $groupPriceValue = $connection->getCheckSql("MIN(o.is_require) > 0", $groupPriceMin, 0); - $groupPrice = $connection->getCheckSql("MIN(i.base_group_price) IS NOT NULL", $groupPriceValue, "NULL"); - $maxPriceRound = new \Zend_Db_Expr("ROUND(i.price * ({$optPriceValue} / 100), 4)"); $maxPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $maxPriceRound); $maxPrice = $connection->getCheckSql( @@ -461,7 +446,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr 'min_price' => $minPrice, 'max_price' => $maxPrice, 'tier_price' => $tierPrice, - 'group_price' => $groupPrice, ] ); @@ -511,17 +495,11 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr $tierPriceValue = $connection->getCheckSql("{$tierPriceExpr} > 0 AND o.is_require > 0", $tierPriceExpr, 0); $tierPrice = $connection->getCheckSql("i.base_tier IS NOT NULL", $tierPriceValue, "NULL"); - $groupPriceRound = new \Zend_Db_Expr("ROUND(i.base_group_price * ({$optPriceValue} / 100), 4)"); - $groupPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $groupPriceRound); - $groupPriceValue = $connection->getCheckSql("{$groupPriceExpr} > 0 AND o.is_require > 0", $groupPriceExpr, 0); - $groupPrice = $connection->getCheckSql("i.base_group_price IS NOT NULL", $groupPriceValue, "NULL"); - $select->columns( [ 'min_price' => $minPrice, 'max_price' => $maxPrice, 'tier_price' => $tierPrice, - 'group_price' => $groupPrice, ] ); @@ -537,7 +515,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr 'min_price' => 'SUM(min_price)', 'max_price' => 'SUM(max_price)', 'tier_price' => 'SUM(tier_price)', - 'group_price' => 'SUM(group_price)' ] )->group( ['entity_id', 'customer_group_id', 'website_id'] @@ -561,11 +538,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr 'i.tier_price + io.tier_price', 'NULL' ), - 'group_price' => $connection->getCheckSql( - 'i.group_price IS NOT NULL', - 'i.group_price + io.group_price', - 'NULL' - ), ] ); $query = $select->crossUpdateFromSelect($table); @@ -594,7 +566,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr 'min_price' => 'min_price', 'max_price' => 'max_price', 'tier_price' => 'tier_price', - 'group_price' => 'group_price', ]; $connection = $this->getConnection(); @@ -619,16 +590,6 @@ class DefaultPrice extends \Magento\Catalog\Model\Resource\Product\Indexer\Abstr return $this->getTable('catalog_product_index_tier_price'); } - /** - * Retrieve table name for product group price index - * - * @return string - */ - protected function _getGroupPriceIndexTable() - { - return $this->getTable('catalog_product_index_group_price'); - } - /** * Retrieve temporary index table name * diff --git a/app/code/Magento/Catalog/Pricing/Price/GroupPrice.php b/app/code/Magento/Catalog/Pricing/Price/GroupPrice.php deleted file mode 100644 index aafc7fd055a9d6cdb98d3a402bf19fc6d75c95bf..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Pricing/Price/GroupPrice.php +++ /dev/null @@ -1,111 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Pricing\Price; - -use Magento\Catalog\Model\Product; -use Magento\Customer\Model\Session; -use Magento\Framework\Pricing\Adjustment\CalculatorInterface; -use Magento\Framework\Pricing\Price\AbstractPrice; -use Magento\Framework\Pricing\Price\BasePriceProviderInterface; - -/** - * Group price model - */ -class GroupPrice extends AbstractPrice implements BasePriceProviderInterface -{ - /** - * Price type group - */ - const PRICE_CODE = 'group_price'; - - /** - * @var Session - */ - protected $customerSession; - - /** - * @var array|null - */ - protected $storedGroupPrice; - - /** - * @param Product $saleableItem - * @param float $quantity - * @param CalculatorInterface $calculator - * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency - * @param Session $customerSession - */ - public function __construct( - Product $saleableItem, - $quantity, - CalculatorInterface $calculator, - \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, - Session $customerSession - ) { - parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); - $this->customerSession = $customerSession; - } - - /** - * @return float|bool - */ - public function getValue() - { - if ($this->value === null) { - $this->value = false; - $customerGroup = $this->getCustomerGroupId(); - foreach ($this->getStoredGroupPrice() as $groupPrice) { - if ($groupPrice['cust_group'] == $customerGroup) { - $this->value = (float) $groupPrice['website_price']; - if (!$this->isPercentageDiscount()) { - $this->value = $this->priceCurrency->convertAndRound($this->value); - } - break; - } - } - } - return $this->value; - } - - /** - * @return int - */ - protected function getCustomerGroupId() - { - if ($this->product->getCustomerGroupId() !== null) { - return (int) $this->product->getCustomerGroupId(); - } - return (int) $this->customerSession->getCustomerGroupId(); - } - - /** - * @return array - */ - protected function getStoredGroupPrice() - { - if (null === $this->storedGroupPrice) { - $resource = $this->product->getResource(); - $attribute = $resource->getAttribute('group_price'); - if ($attribute) { - $attribute->getBackend()->afterLoad($this->product); - $this->storedGroupPrice = $this->product->getData('group_price'); - } - if (null === $this->storedGroupPrice || !is_array($this->storedGroupPrice)) { - $this->storedGroupPrice = []; - } - } - return $this->storedGroupPrice; - } - - /** - * @return bool - */ - public function isPercentageDiscount() - { - return false; - } -} diff --git a/app/code/Magento/Catalog/Pricing/Price/TierPrice.php b/app/code/Magento/Catalog/Pricing/Price/TierPrice.php index 1a7e1dcc8c24b27ff0c5f35f71ecb7cd110b3dba..3f5737dbf5ad87fed8482cb91d160b55325b4116 100644 --- a/app/code/Magento/Catalog/Pricing/Price/TierPrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/TierPrice.php @@ -160,10 +160,15 @@ class TierPrice extends AbstractPrice implements TierPriceInterface, BasePricePr protected function filterTierPrices(array $priceList) { $qtyCache = []; - foreach ($priceList as $priceKey => $price) { + $allCustomersGroupId = $this->groupManagement->getAllCustomersGroup()->getId(); + foreach ($priceList as $priceKey => &$price) { + if (isset($price['price_qty']) && $price['price_qty'] == 1) { + unset($priceList[$priceKey]); + continue; + } /* filter price by customer group */ if ($price['cust_group'] != $this->customerGroup && - $price['cust_group'] != $this->groupManagement->getAllCustomersGroup()->getId()) { + $price['cust_group'] != $allCustomersGroupId) { unset($priceList[$priceKey]); continue; } @@ -223,11 +228,11 @@ class TierPrice extends AbstractPrice implements TierPriceInterface, BasePricePr */ protected function canApplyTierPrice(array $currentTierPrice, $prevPriceGroup, $prevQty) { - $custGroupAllId = $this->groupManagement->getAllCustomersGroup()->getId(); + $custGroupAllId = (int)$this->groupManagement->getAllCustomersGroup()->getId(); // Tier price can be applied, if: // tier price is for current customer group or is for all groups - if ($currentTierPrice['cust_group'] !== $this->customerGroup - && $currentTierPrice['cust_group'] !== $custGroupAllId + if ((int)$currentTierPrice['cust_group'] !== $this->customerGroup + && (int)$currentTierPrice['cust_group'] !== $custGroupAllId ) { return false; } diff --git a/app/code/Magento/Catalog/Setup/CategorySetup.php b/app/code/Magento/Catalog/Setup/CategorySetup.php index 77a0d20c1ee5c2d8c11bcff388c85414260f09f6..0c173c5c2c8df89c2380c14e401df540d111a851 100644 --- a/app/code/Magento/Catalog/Setup/CategorySetup.php +++ b/app/code/Magento/Catalog/Setup/CategorySetup.php @@ -577,20 +577,6 @@ class CategorySetup extends EavSetup 'group' => 'Images', ], 'old_id' => ['type' => 'int', 'required' => false, 'sort_order' => 6, 'visible' => false], - 'group_price' => [ - 'type' => 'decimal', - 'label' => 'Group Price', - 'input' => 'text', - 'backend' => 'Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice', - 'required' => false, - 'sort_order' => 2, - 'global' => \Magento\Catalog\Model\Resource\Eav\Attribute::SCOPE_WEBSITE, - 'apply_to' => 'simple,virtual', - 'group' => 'Prices', - 'is_used_in_grid' => true, - 'is_visible_in_grid' => false, - 'is_filterable_in_grid' => true, - ], 'tier_price' => [ 'type' => 'decimal', 'label' => 'Tier Price', diff --git a/app/code/Magento/Catalog/Setup/InstallSchema.php b/app/code/Magento/Catalog/Setup/InstallSchema.php index adc00706d70791e163480d7dffccb8df173f61e8..c507f35db664497d5a59c4f45a5e458833dde4d2 100644 --- a/app/code/Magento/Catalog/Setup/InstallSchema.php +++ b/app/code/Magento/Catalog/Setup/InstallSchema.php @@ -2987,13 +2987,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->addIndex( $installer->getIdxName('catalog_product_index_price', ['customer_group_id']), ['customer_group_id'] @@ -3217,13 +3210,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setComment( 'Catalog Product Price Indexer Config Option Aggregate Index Table' ); @@ -3279,13 +3265,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -3345,13 +3324,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setComment( 'Catalog Product Price Indexer Config Option Index Table' ); @@ -3407,13 +3379,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -3501,20 +3466,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Base Tier' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) - ->addColumn( - 'base_group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Base Group Price' - ) ->setComment( 'Catalog Product Price Indexer Final Index Table' ); @@ -3598,20 +3549,7 @@ class InstallSchema implements InstallSchemaInterface [], 'Base Tier' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) - ->addColumn( - 'base_group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Base Group Price' - ) + ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -3671,13 +3609,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setComment( 'Catalog Product Price Indexer Option Index Table' ); @@ -3733,13 +3664,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -3806,13 +3730,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setComment( 'Catalog Product Price Indexer Option Aggregate Index Table' ); @@ -3875,13 +3792,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->setOption( 'type', \Magento\Framework\DB\Adapter\Pdo\Mysql::ENGINE_MEMORY @@ -4182,13 +4092,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->addIndex( $installer->getIdxName('catalog_product_index_price_idx', ['customer_group_id']), ['customer_group_id'] @@ -4277,13 +4180,6 @@ class InstallSchema implements InstallSchemaInterface [], 'Tier Price' ) - ->addColumn( - 'group_price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Group price' - ) ->addIndex( $installer->getIdxName('catalog_product_index_price_tmp', ['customer_group_id']), ['customer_group_id'] @@ -4369,195 +4265,7 @@ class InstallSchema implements InstallSchemaInterface $installer->getConnection() ->createTable($table); - /** - * Create table 'catalog_product_entity_group_price' - */ - $table = $installer->getConnection() - ->newTable( - $installer->getTable('catalog_product_entity_group_price') - ) - ->addColumn( - 'value_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['identity' => true, 'nullable' => false, 'primary' => true], - 'Value ID' - ) - ->addColumn( - 'entity_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Entity ID' - ) - ->addColumn( - 'all_groups', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '1'], - 'Is Applicable To All Customer Groups' - ) - ->addColumn( - 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'default' => '0'], - 'Customer Group ID' - ) - ->addColumn( - 'value', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - ['nullable' => false, 'default' => '0.0000'], - 'Value' - ) - ->addColumn( - 'website_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false], - 'Website ID' - ) - ->addIndex( - $installer->getIdxName( - 'catalog_product_entity_group_price', - ['entity_id', 'all_groups', 'customer_group_id', 'website_id'], - \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE - ), - ['entity_id', 'all_groups', 'customer_group_id', 'website_id'], - ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] - ) - ->addIndex( - $installer->getIdxName('catalog_product_entity_group_price', ['customer_group_id']), - ['customer_group_id'] - ) - ->addIndex( - $installer->getIdxName('catalog_product_entity_group_price', ['website_id']), - ['website_id'] - ) - ->addForeignKey( - $installer->getFkName( - 'catalog_product_entity_group_price', - 'customer_group_id', - 'customer_group', - 'customer_group_id' - ), - 'customer_group_id', - $installer->getTable('customer_group'), - 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - ) - ->addForeignKey( - $installer->getFkName( - 'catalog_product_entity_group_price', - 'entity_id', - 'catalog_product_entity', - 'entity_id' - ), - 'entity_id', - $installer->getTable('catalog_product_entity'), - 'entity_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - ) - ->addForeignKey( - $installer->getFkName( - 'catalog_product_entity_group_price', - 'website_id', - 'store_website', - 'website_id' - ), - 'website_id', - $installer->getTable('store_website'), - 'website_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - ) - ->setComment( - 'Catalog Product Group Price Attribute Backend Table' - ); - $installer->getConnection() - ->createTable($table); - - /** - * Create table 'catalog_product_index_group_price' - */ - - $table = $installer->getConnection() - ->newTable( - $installer->getTable('catalog_product_index_group_price') - ) - ->addColumn( - 'entity_id', - \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, - null, - ['unsigned' => true, 'nullable' => false, 'primary' => true], - 'Entity ID' - ) - ->addColumn( - 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'primary' => true], - 'Customer Group ID' - ) - ->addColumn( - 'website_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, - null, - ['unsigned' => true, 'nullable' => false, 'primary' => true], - 'Website ID' - ) - ->addColumn( - 'price', - \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, - '12,4', - [], - 'Min Price' - ) - ->addIndex( - $installer->getIdxName('catalog_product_index_group_price', ['customer_group_id']), - ['customer_group_id'] - ) - ->addIndex( - $installer->getIdxName('catalog_product_index_group_price', ['website_id']), - ['website_id'] - ) - ->addForeignKey( - $installer->getFkName( - 'catalog_product_index_group_price', - 'customer_group_id', - 'customer_group', - 'customer_group_id' - ), - 'customer_group_id', - $installer->getTable('customer_group'), - 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - ) - ->addForeignKey( - $installer->getFkName( - 'catalog_product_index_group_price', - 'entity_id', - 'catalog_product_entity', - 'entity_id' - ), - 'entity_id', - $installer->getTable('catalog_product_entity'), - 'entity_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - ) - ->addForeignKey( - $installer->getFkName('catalog_product_index_group_price', 'website_id', 'store_website', 'website_id'), - 'website_id', - $installer->getTable('store_website'), - 'website_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE - ) - ->setComment( - 'Catalog Product Group Price Index Table' - ); - $installer->getConnection() - ->createTable($table); - $installer->endSetup(); + $installer->endSetup(); } } diff --git a/app/code/Magento/Catalog/Setup/UpgradeData.php b/app/code/Magento/Catalog/Setup/UpgradeData.php new file mode 100644 index 0000000000000000000000000000000000000000..5adf385101fc2e67d571c6a3421c46940c685ff8 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/UpgradeData.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Setup; + +use Magento\Framework\Setup\UpgradeDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; + +/** + * Upgrade Data script + * @codeCoverageIgnore + */ +class UpgradeData implements UpgradeDataInterface +{ + /** + * Category setup factory + * + * @var CategorySetupFactory + */ + private $categorySetupFactory; + + /** + * Init + * + * @param CategorySetupFactory $categorySetupFactory + */ + public function __construct(CategorySetupFactory $categorySetupFactory) + { + $this->categorySetupFactory = $categorySetupFactory; + } + + /** + * {@inheritdoc} + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + if (version_compare($context->getVersion(), '2.0.1') < 0) { + /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); + + $entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); + $attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId); + + $attributeGroupId = $categorySetup->getAttributeGroupId($entityTypeId, $attributeSetId, 'Images'); + + // update General Group + $categorySetup->updateAttributeGroup( + $entityTypeId, + $attributeSetId, + $attributeGroupId, + 'attribute_group_name', + 'Images and Videos' + ); + $select = $setup->getConnection()->select() + ->from( + $setup->getTable('catalog_product_entity_group_price'), + [ + 'value_id', + 'entity_id', + 'all_groups', + 'customer_group_id', + new \Zend_Db_Expr('1'), + 'value', + 'website_id' + ] + ); + $setup->getConnection()->insertFromSelect( + $select, + $setup->getTable('catalog_product_entity_group_price'), + [ + 'value_id', + 'entity_id', + 'all_groups', + 'customer_group_id', + 'qty', + 'value', + 'website_id' + ] + ); + $categorySetupManager = $this->categorySetupFactory->create(); + $categorySetupManager->removeAttribute(\Magento\Catalog\Model\Product::ENTITY, 'group_price'); + } + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Catalog/Setup/UpgradeSchema.php b/app/code/Magento/Catalog/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..fe978e80a9807df85c09ff8d94bea42a9bd64280 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/UpgradeSchema.php @@ -0,0 +1,256 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Setup; + +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media; +use Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter; + +/** + * Upgrade the Catalog module DB scheme + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $this->addSupportVideoMediaAttributes($setup); + $this->removeGroupPrice($setup); + } + + $setup->endSetup(); + } + + /** + * + * @param SchemaSetupInterface $setup + * @return void + */ + protected function createValueToEntityTable(SchemaSetupInterface $setup) + { + /** + * Create table 'catalog_product_entity_media_gallery_value_to_entity' + */ + $table = $setup->getConnection() + ->newTable($setup->getTable(Media::GALLERY_VALUE_TO_ENTITY_TABLE)) + ->addColumn( + 'value_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false], + 'Value media Entry ID' + ) + ->addColumn( + 'entity_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false], + 'Product entity ID' + ) + ->addIndex( + $setup->getIdxName( + Media::GALLERY_VALUE_TO_ENTITY_TABLE, + ['value_id', 'entity_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['value_id', 'entity_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addForeignKey( + $setup->getFkName( + Media::GALLERY_VALUE_TO_ENTITY_TABLE, + 'value_id', + Media::GALLERY_TABLE, + 'value_id' + ), + 'value_id', + $setup->getTable(Media::GALLERY_TABLE), + 'value_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName( + Media::GALLERY_VALUE_TO_ENTITY_TABLE, + 'entity_id', + 'catalog_product_entity', + 'entity_id' + ), + 'entity_id', + $setup->getTable('catalog_product_entity'), + 'entity_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment('Link Media value to Product entity table'); + $setup->getConnection()->createTable($table); + } + + /** + * + * @param SchemaSetupInterface $setup + * @return void + */ + protected function addForeignKeys(SchemaSetupInterface $setup) + { + /** + * Add foreign keys again + */ + $setup->getConnection()->addForeignKey( + $setup->getFkName( + Media::GALLERY_VALUE_TABLE, + 'value_id', + Media::GALLERY_TABLE, + 'value_id' + ), + $setup->getTable(Media::GALLERY_VALUE_TABLE), + 'value_id', + $setup->getTable(Media::GALLERY_TABLE), + 'value_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ); + + $setup->getConnection()->addForeignKey( + $setup->getFkName( + Media::GALLERY_VALUE_TABLE, + 'store_id', + $setup->getTable('store'), + 'store_id' + ), + $setup->getTable(Media::GALLERY_VALUE_TABLE), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ); + } + + /** + * @param SchemaSetupInterface $setup + * @return void + */ + private function addSupportVideoMediaAttributes(SchemaSetupInterface $setup) + { + /** Add support video media attribute */ + $this->createValueToEntityTable($setup); + /** + * Add media type property to the Gallery entry table + */ + $setup->getConnection()->addColumn( + $setup->getTable(Media::GALLERY_TABLE), + 'media_type', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'length' => 32, + 'nullable' => false, + 'default' => ImageEntryConverter::MEDIA_TYPE_CODE, + 'comment' => 'Media entry type' + ] + ); + $setup->getConnection()->addColumn( + $setup->getTable(Media::GALLERY_TABLE), + 'disabled', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => 0, + 'comment' => 'Visibility status' + ] + ); + + /** + * Drop entity Id columns + */ + $setup->getConnection()->dropColumn($setup->getTable(Media::GALLERY_TABLE), 'entity_id'); + + /** + * Drop primary index + */ + $setup->getConnection()->dropForeignKey( + $setup->getTable(Media::GALLERY_VALUE_TABLE), + $setup->getFkName( + Media::GALLERY_VALUE_TABLE, + 'value_id', + Media::GALLERY_TABLE, + 'value_id' + ) + ); + $setup->getConnection()->dropForeignKey( + $setup->getTable(Media::GALLERY_VALUE_TABLE), + $setup->getFkName( + Media::GALLERY_VALUE_TABLE, + 'store_id', + 'store', + 'store_id' + ) + ); + $setup->getConnection()->dropIndex($setup->getTable(Media::GALLERY_VALUE_TABLE), 'primary'); + $setup->getConnection()->addColumn( + $setup->getTable(Media::GALLERY_VALUE_TABLE), + 'record_id', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + 'primary' => true, + 'auto_increment' => true, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Record Id' + ] + ); + + /** + * Add index 'value_id' + */ + $setup->getConnection()->addIndex( + $setup->getTable(Media::GALLERY_VALUE_TABLE), + $setup->getConnection()->getIndexName( + $setup->getTable(Media::GALLERY_VALUE_TABLE), + 'value_id', + 'index' + ), + 'value_id' + ); + $this->addForeignKeys($setup); + } + + /** + * Remove Group Price + * @param SchemaSetupInterface $setup + * @return void + */ + private function removeGroupPrice(SchemaSetupInterface $setup) + { + $connection = $setup->getConnection(); + $fields = [ + ['table' => 'catalog_product_index_price_final_idx', 'column' => 'base_group_price'], + ['table' => 'catalog_product_index_price_final_tmp', 'column' => 'base_group_price'], + ['table' => 'catalog_product_index_price', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_cfg_opt_agr_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_cfg_opt_agr_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_cfg_opt_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_cfg_opt_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_final_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_final_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_opt_agr_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_opt_agr_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_opt_idx', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_opt_tmp', 'column' => 'group_price'], + ['table' => 'catalog_product_index_price_tmp', 'column' => 'group_price'], + ]; + + foreach ($fields as $filedInfo) { + $connection->dropColumn($setup->getTable($filedInfo['table']), $filedInfo['column']); + } + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/WeightTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/WeightTest.php index 173d209c2278d94736c0da8e5387b664db3ede2a..c5cb5700c445e6239aee3fcf44fddc157f2ba599 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/WeightTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/WeightTest.php @@ -7,63 +7,87 @@ namespace Magento\Catalog\Test\Unit\Block\Adminhtml\Product\Helper\Form; class WeightTest extends \PHPUnit_Framework_TestCase { - const VIRTUAL_FIELD_HTML_ID = 'weight_and_type_switcher'; - /** * @var \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight */ protected $_model; /** - * @var \Magento\Framework\Data\Form\Element\Checkbox + * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_virtual; + protected $weightSwitcher; - public function testSetForm() - { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $factory; - $factory = $this->getMock('Magento\Framework\Data\Form\Element\Factory', [], [], '', false); + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $collectionFactory; - $collectionFactory = $this->getMock( - 'Magento\Framework\Data\Form\Element\CollectionFactory', - ['create'], + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormat; + + protected function setUp() + { + if (version_compare('5.5.28', phpversion(), '=')) { + $this->markTestSkipped('MAGETWO-43290: This test fails with Segmentation fault on PHP 5.5.28'); + } + $this->weightSwitcher = $this->getMock( + 'Magento\Framework\Data\Form\Element\Radios', + ['setId', 'setName', 'setLabel', 'setForm'], [], '', false ); - $formKey = $this->getMock('Magento\Framework\Data\Form\FormKey', [], [], '', false); + $this->weightSwitcher->expects($this->any())->method('setId')->will($this->returnSelf()); + $this->weightSwitcher->expects($this->any())->method('setName')->will($this->returnSelf()); + $this->weightSwitcher->expects($this->any())->method('setLabel')->will($this->returnSelf()); - $form = new \Magento\Framework\Data\Form($factory, $collectionFactory, $formKey); - - $helper = $this->getMock( - 'Magento\Catalog\Helper\Product', - ['getTypeSwitcherControlLabel'], + $this->factory = $this->getMock('Magento\Framework\Data\Form\Element\Factory', [], [], '', false); + $this->factory->expects( + $this->once() + )->method( + 'create' + )->with( + $this->equalTo('radios') + )->will( + $this->returnValue($this->weightSwitcher) + ); + $this->localeFormat = $this->getMock( + '\Magento\Framework\Locale\Format', + [], [], '', - false, false ); - $helper->expects( - $this->any() - )->method( - 'getTypeSwitcherControlLabel' - )->will( - $this->returnValue('Virtual / Downloadable') - ); - $this->_virtual = $this->getMock( - 'Magento\Framework\Data\Form\Element\Checkbox', - ['setId', 'setName', 'setLabel', 'setForm'], + $this->collectionFactory = $this->getMock( + 'Magento\Framework\Data\Form\Element\CollectionFactory', + ['create'], [], '', - false, false ); - $this->_virtual->expects($this->any())->method('setId')->will($this->returnSelf()); - $this->_virtual->expects($this->any())->method('setName')->will($this->returnSelf()); - $this->_virtual->expects($this->any())->method('setLabel')->will($this->returnSelf()); - $this->_virtual->expects( + + $this->_model = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + 'Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight', + [ + 'factoryElement' => $this->factory, + 'factoryCollection' => $this->collectionFactory, + 'localeFormat' => $this->localeFormat + ] + ); + } + + public function testSetForm() + { + $form = $this->getMock('Magento\Framework\Data\Form', [], [], '', false); + $this->weightSwitcher->expects( $this->any() )->method( 'setForm' @@ -73,44 +97,12 @@ class WeightTest extends \PHPUnit_Framework_TestCase $this->returnSelf() ); - $factory = $this->getMock('Magento\Framework\Data\Form\Element\Factory', [], [], '', false); - $factory->expects( - $this->once() - )->method( - 'create' - )->with( - $this->equalTo('checkbox') - )->will( - $this->returnValue($this->_virtual) - ); - - $this->_model = $objectManager->getObject( - 'Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight', - ['factoryElement' => $factory, 'factoryCollection' => $collectionFactory, 'helper' => $helper] - ); - $this->_model->setForm($form); } public function testGetEscapedValue() { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $collectionFactory = $this->getMock( - 'Magento\Framework\Data\Form\Element\CollectionFactory', - ['create'], - [], - '', - false - ); - $localeFormat = $this->getMock( - '\Magento\Framework\Locale\Format', - [], - [], - '', - false - ); - $localeFormat->expects( + $this->localeFormat->expects( $this->any() )->method( 'getPriceFormat' @@ -120,55 +112,6 @@ class WeightTest extends \PHPUnit_Framework_TestCase 'groupSymbol' => '.', ]); - $helper = $this->getMock( - 'Magento\Catalog\Helper\Product', - ['getTypeSwitcherControlLabel'], - [], - '', - false, - false - ); - $helper->expects( - $this->any() - )->method( - 'getTypeSwitcherControlLabel' - )->will( - $this->returnValue('Virtual / Downloadable') - ); - - $this->_virtual = $this->getMock( - 'Magento\Framework\Data\Form\Element\Checkbox', - ['setId', 'setName', 'setLabel'], - [], - '', - false, - false - ); - $this->_virtual->expects($this->any())->method('setId')->will($this->returnSelf()); - $this->_virtual->expects($this->any())->method('setName')->will($this->returnSelf()); - $this->_virtual->expects($this->any())->method('setLabel')->will($this->returnSelf()); - - $factory = $this->getMock('Magento\Framework\Data\Form\Element\Factory', [], [], '', false); - $factory->expects( - $this->once() - )->method( - 'create' - )->with( - $this->equalTo('checkbox') - )->will( - $this->returnValue($this->_virtual) - ); - - $this->_model = $objectManager->getObject( - 'Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight', - [ - 'factoryElement' => $factory, - 'factoryCollection' => $collectionFactory, - 'helper' => $helper, - 'localeFormat' => $localeFormat - ] - ); - $this->_model->setValue('30000.4'); $this->_model->setEntityAttribute(true); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php index 01eb34bdefbbdb14f43ed17842012d4e32b68f8d..ab33d43f273c1325c50b41d258c293b237b9a30d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php @@ -67,12 +67,12 @@ class AbstractTest extends \PHPUnit_Framework_TestCase $attribute->expects($this->any())->method('getBackendTable')->will($this->returnValue('table')); - $attribute->expects($this->any())->method('getName')->will($this->returnValue('group_price')); + $attribute->expects($this->any())->method('getName')->will($this->returnValue('tear_price')); $this->_model->setAttribute($attribute); $object = new \Magento\Framework\DataObject(); - $object->setGroupPrice([['price_id' => 10]]); + $object->setTearPrice([['price_id' => 10]]); $object->setId(555); $this->assertEquals( diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPriceTest.php deleted file mode 100644 index 3b12c10fbfb11c6181ee300a8f7584c0dded3145..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPriceTest.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Test\Unit\Model\Product\Attribute\Backend; - -class GroupPriceTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice - */ - protected $model; - - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - protected $objectHelper; - - protected function setUp() - { - $this->objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->model = $this->objectHelper->getObject('Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice'); - } - - public function testIsScaler() - { - $this->assertFalse($this->model->isScalar(), 'Attribute GroupPrice should not be scaler'); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2e95748ac7e531c414db7e6497c2bca9a1fa4276 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/EntryConverterPoolTest.php @@ -0,0 +1,117 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\Product\Attribute\Backend\Media; + +/** + * Class aggregate all Media Gallery Entry Converters + */ +class EntryConverterPoolTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter + */ + protected $imageMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter + */ + protected $videoMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\DataObject + */ + protected $dataObjectMock; + + public function setUp() + { + $this->imageMock = + $this->getMock( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter', + [], + [], + '', + false + ); + + $this->imageMock->expects($this->any())->method('getMediaEntryType')->willReturn('image'); + + $this->videoMock = + $this->getMock( + '\Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter', + [], + [], + '', + false + ); + + $this->videoMock->expects($this->any())->method('getMediaEntryType')->willReturn('external-video'); + + $this->dataObjectMock = $this->getMock('\Magento\Framework\DataObject', [], [], '', false); + } + + public function testGetConverterByMediaTypeImage() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $converterPool = $objectManager->getObject( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool', + [ + 'mediaGalleryEntryConvertersCollection' => [$this->imageMock, $this->videoMock] + ] + ); + + $converterPool->getConverterByMediaType('image'); + } + + public function testGetConverterByMediaTypeVideo() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $converterPool = $objectManager->getObject( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool', + [ + 'mediaGalleryEntryConvertersCollection' => [$this->imageMock, $this->videoMock] + ] + ); + + $converterPool->getConverterByMediaType('external-video'); + } + + public function testConstructException() + { + $this->setExpectedException('\InvalidArgumentException'); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $converterPool = $objectManager->getObject( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool', + [ + 'mediaGalleryEntryConvertersCollection' => [$this->dataObjectMock] + ] + ); + + $converterPool->getConverterByMediaType('external-video'); + } + + public function testGetConverterByMediaTypeImageException() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $converterPool = $objectManager->getObject( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool', + [ + 'mediaGalleryEntryConvertersCollection' => [$this->imageMock, $this->videoMock] + ] + ); + + $this->setExpectedException('\Magento\Framework\Exception\LocalizedException'); + + $converterPool->getConverterByMediaType('something_wrong'); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/ImageEntryConverterTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/ImageEntryConverterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..592a95b73ad98d06842bbd4fc4cb0ddd05da509f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/Media/ImageEntryConverterTest.php @@ -0,0 +1,210 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\Product\Attribute\Backend\Media; + +class ImageEntryConverterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory + */ + protected $mediaGalleryEntryFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntry + */ + protected $mediaGalleryEntryMock; + + /** + * @var \Magento\Framework\Api\DataObjectHelper + * |\PHPUnit_Framework_MockObject_MockObject + */ + protected $dataObjectHelperMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product + */ + protected $productMock; + + /** + * @var \Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter + * |\Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $modelObject; + + + public function setUp() + { + $this->mediaGalleryEntryFactoryMock = + $this->getMock( + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory', + ['create'], + [], + '', + false + ); + + $this->mediaGalleryEntryMock = + $this->getMock( + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface', + [ + 'getId', + 'setId', + 'getMediaType', + 'setMediaType', + 'getLabel', + 'setLabel', + 'getPosition', + 'setPosition', + 'isDisabled', + 'setDisabled', + 'getTypes', + 'setTypes', + 'getFile', + 'setFile', + 'getContent', + 'setContent', + 'getExtensionAttributes', + 'setExtensionAttributes' + ], + [], + '', + false + ); + + $this->mediaGalleryEntryFactoryMock->expects($this->any())->method('create')->willReturn( + $this->mediaGalleryEntryMock + ); + + $this->dataObjectHelperMock = $this->getMock('\Magento\Framework\Api\DataObjectHelper', [], [], '', false); + + $this->productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->modelObject = $objectManager->getObject( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter', + [ + 'mediaGalleryEntryFactory' => $this->mediaGalleryEntryFactoryMock, + 'dataObjectHelper' => $this->dataObjectHelperMock + ] + ); + } + + public function testGetMediaEntryType() + { + $this->assertEquals($this->modelObject->getMediaEntryType(), 'image'); + } + + public function testConvertTo() + { + $rowData = [ + 'value_id' => '6', + 'file' => '/s/a/sample-1_1.jpg', + 'media_type' => 'image', + 'entity_id' => '1', + 'label' => '', + 'position' => '5', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '5', + 'disabled_default' => '0', + ]; + + $productImages = [ + 'image' => '/s/a/sample_3.jpg', + 'small_image' => '/s/a/sample-1_1.jpg', + 'thumbnail' => '/s/a/sample-1_1.jpg', + 'swatch_image' => '/s/a/sample_3.jpg', + ]; + + $this->productMock->expects($this->any())->method('getMediaAttributeValues')->willReturn($productImages); + + $this->modelObject->convertTo($this->productMock, $rowData); + } + + public function testConvertFromNullContent() + { + $this->mediaGalleryEntryMock->expects($this->once())->method('getId')->willReturn('5'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getFile')->willReturn('/s/a/sample_3.jpg'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getLabel')->willReturn(''); + $this->mediaGalleryEntryMock->expects($this->once())->method('getPosition')->willReturn('4'); + $this->mediaGalleryEntryMock->expects($this->once())->method('isDisabled')->willReturn('0'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getTypes')->willReturn( + [ + 0 => 'image', + 1 => 'swatch_image', + ] + ); + $this->mediaGalleryEntryMock->expects($this->once())->method('getContent')->willReturn(null); + + $expectedResult = [ + 'value_id' => '5', + 'file' => '/s/a/sample_3.jpg', + 'label' => '', + 'position' => '4', + 'disabled' => '0', + 'types' => + [ + 0 => 'image', + 1 => 'swatch_image', + ], + 'content' => null, + 'media_type' => null, + ]; + + $this->assertEquals($expectedResult, $this->modelObject->convertFrom($this->mediaGalleryEntryMock)); + } + + public function testConvertFrom() + { + $this->mediaGalleryEntryMock->expects($this->once())->method('getId')->willReturn('5'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getFile')->willReturn('/s/a/sample_3.jpg'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getLabel')->willReturn(''); + $this->mediaGalleryEntryMock->expects($this->once())->method('getPosition')->willReturn('4'); + $this->mediaGalleryEntryMock->expects($this->once())->method('isDisabled')->willReturn('0'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getTypes')->willReturn( + [ + 0 => 'image', + 1 => 'swatch_image', + ] + ); + $imageContentInterface = $this->getMock('Magento\Framework\Api\Data\ImageContentInterface', [], [], '', false); + + $imageContentInterface->expects($this->once())->method('getBase64EncodedData')->willReturn( + base64_encode('some_content') + ); + $imageContentInterface->expects($this->once())->method('getType')->willReturn('image/jpeg'); + $imageContentInterface->expects($this->once())->method('getName')->willReturn('/s/a/sample_3.jpg'); + + $this->mediaGalleryEntryMock->expects($this->once())->method('getContent')->willReturn($imageContentInterface); + + $expectedResult = [ + 'value_id' => '5', + 'file' => '/s/a/sample_3.jpg', + 'label' => '', + 'position' => '4', + 'disabled' => '0', + 'types' => + [ + 0 => 'image', + 1 => 'swatch_image', + ], + 'content' => [ + 'data' => [ + 'base64_encoded_data' => base64_encode('some_content'), + 'type' => 'image/jpeg', + 'name' => '/s/a/sample_3.jpg' + ] + ], + 'media_type' => null, + ]; + + $this->assertEquals($expectedResult, $this->modelObject->convertFrom($this->mediaGalleryEntryMock)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/MediaTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/MediaTest.php index 5ff3d8b2d5278d98c794a672f2cdf424c4d57718..a68b53ca6cf663b2aa6e04d84d6c8f19900c343a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/MediaTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/MediaTest.php @@ -131,168 +131,6 @@ class MediaTest extends \PHPUnit_Framework_TestCase ); } - public function testAfterSaveDuplicate() - { - $attributeCode = 'test_code'; - $attributeMock = $this->getMockBuilder('Magento\Eav\Model\Entity\Attribute') - ->disableOriginalConstructor() - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getAttributeCode') - ->will($this->returnValue($attributeCode)); - - $this->dataObject->expects($this->once()) - ->method('getIsDuplicate') - ->will($this->returnValue(true)); - $this->dataObject->setData($attributeCode, []); - - $this->model->setAttribute($attributeMock); - $this->assertNull($this->model->afterSave($this->dataObject)); - } - - public function testAfterSaveNoAttribute() - { - $attributeCode = 'test_code'; - $attributeMock = $this->getMockBuilder('Magento\Eav\Model\Entity\Attribute') - ->disableOriginalConstructor() - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getAttributeCode') - ->will($this->returnValue($attributeCode)); - - $this->dataObject->expects($this->once()) - ->method('getIsDuplicate') - ->will($this->returnValue(false)); - $this->dataObject->setData($attributeCode, []); - - $this->model->setAttribute($attributeMock); - $this->assertNull($this->model->afterSave($this->dataObject)); - } - - public function testAfterSaveDeleteFiles() - { - $storeId = 1; - $storeIds = ['store_1' => 1, 'store_2' => 2]; - $attributeCode = 'test_code'; - $toDelete = [1]; - $mediaPath = 'catalog/media'; - $filePathToRemove = $mediaPath . '/file/path'; - $attributeValue = [ - 'images' => [ - [ - 'removed' => true, - 'value_id' => 1, - 'file' => 'file/path', - ], - [ - 'removed' => false, - 'value_id' => 1, - 'file' => 'file/path2' - ], - ], - ]; - $assignedImages = [ - ['filepath' => 'path_to_image'], - ]; - - $attributeMock = $this->getMockBuilder('Magento\Eav\Model\Entity\Attribute') - ->disableOriginalConstructor() - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getAttributeCode') - ->will($this->returnValue($attributeCode)); - - $this->dataObject->expects($this->once()) - ->method('getIsDuplicate') - ->will($this->returnValue(false)); - $this->dataObject->expects($this->once()) - ->method('isLockedAttribute') - ->will($this->returnValue(false)); - $this->dataObject->setData($attributeCode, $attributeValue); - $this->dataObject->setId(1); - $this->dataObject->setStoreId($storeId); - $this->dataObject->setStoreIds($storeIds); - - $productMock = $this->getMockBuilder('Magento\Catalog\Model\Product') - ->disableOriginalConstructor() - ->setMethods(['getAssignedImages', '__wakeup']) - ->getMock(); - $productMock->expects($this->any()) - ->method('getAssignedImages') - ->will($this->returnValue($assignedImages)); - - $this->productFactory->expects($this->once()) - ->method('create') - ->will($this->returnValue($productMock)); - - $this->resourceModel->expects($this->once()) - ->method('deleteGallery') - ->with($toDelete); - - $this->mediaConfig->expects($this->once()) - ->method('getBaseMediaPath') - ->will($this->returnValue($mediaPath)); - - $this->mediaDirectory->expects($this->once()) - ->method('delete') - ->with($filePathToRemove); - - $this->model->setAttribute($attributeMock); - $this->assertNull($this->model->afterSave($this->dataObject)); - } - - /** - * @dataProvider afterLoadDataProvider - * @param array $image - */ - public function testAfterLoad($image) - { - $attributeCode = 'attr_code'; - $attribute = $this->getMock( - 'Magento\Eav\Model\Entity\Attribute', - ['getAttributeCode', '__wakeup'], - [], - '', - false - ); - $attribute->expects($this->any())->method('getAttributeCode')->will($this->returnValue($attributeCode)); - $this->resourceModel->expects($this->any())->method('loadGallery')->will($this->returnValue([$image])); - - $this->model->setAttribute($attribute); - $this->model->afterLoad($this->dataObject); - $this->assertEquals([$image], $this->dataObject->getAttrCode('images')); - } - - public function afterLoadDataProvider() - { - return [ - [ - [ - 'label' => 'label_1', - 'position' => 'position_1', - 'disabled' => 'true', - ], - [ - 'label' => 'label_2', - 'position' => 'position_2', - 'disabled' => 'true' - ], - ], - [ - [ - 'label' => null, - 'position' => null, - 'disabled' => null, - ], - [ - 'label' => null, - 'position' => null, - 'disabled' => null - ] - ] - ]; - } - /** * @dataProvider validateDataProvider * @param bool $value @@ -330,73 +168,4 @@ class MediaTest extends \PHPUnit_Framework_TestCase [false] ]; } - - /** - * @dataProvider beforeSaveDataProvider - * @param array $value - */ - public function testBeforeSave($value) - { - $attributeCode = 'attr_code'; - $attribute = $this->getMock( - 'Magento\Eav\Model\Entity\Attribute', - ['getAttributeCode', 'getIsRequired', 'isValueEmpty', 'getIsUnique', 'getEntityType', '__wakeup'], - [], - '', - false - ); - $mediaAttributes = [ - 'image' => $attribute, - 'small_image' => $attribute, - 'thumbnail' => $attribute, - ]; - $attribute->expects($this->any())->method('getAttributeCode')->will($this->returnValue($attributeCode)); - $this->dataObject->expects($this->any())->method('getIsDuplicate')->will($this->returnValue(false)); - $this->model->setAttribute($attribute); - $this->dataObject->setData(['attr_code' => ['images' => $value]]); - $this->dataObject->expects($this->any())->method('getMediaAttributes') - ->will(($this->returnValue($mediaAttributes))); - $this->model->beforeSave($this->dataObject); - foreach ($this->dataObject['attr_code']['images'] as $imageType => $imageData) { - if (isset($imageData['new_file'])) { - $value[$imageType]['file'] = $imageData['file']; - $value[$imageType]['new_file'] = $imageData['new_file']; - } - $this->assertEquals($value[$imageType], $imageData); - } - } - - public function beforeSaveDataProvider() - { - return [ - [ - [ - 'image_1' => [ - 'position' => '1', - 'file' => '/m/y/mydrawing1.jpg.tmp', - 'value_id' => '', - 'label' => 'image 1', - 'disableed' => '0', - 'removed' => '', - ], - 'image_2' => [ - 'position' => '1', - 'file' => '/m/y/mydrawing2.jpg.tmp', - 'value_id' => '', - 'label' => 'image 2', - 'disableed' => '0', - 'removed' => '', - ], - 'image_removed' => [ - 'position' => '1', - 'file' => '/m/y/mydrawing3.jpg.tmp', - 'value_id' => '', - 'label' => 'image 3', - 'disableed' => '0', - 'removed' => '1', - ], - ], - ] - ]; - } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/GroupPriceManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/GroupPriceManagementTest.php deleted file mode 100644 index bb32c9544971521bbd723a33b2f00589531baabd..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/GroupPriceManagementTest.php +++ /dev/null @@ -1,318 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Test\Unit\Model\Product; - -use \Magento\Catalog\Model\Product\GroupPriceManagement; - -use Magento\Framework\Exception\NoSuchEntityException; - -class GroupPriceManagementTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var GroupPriceManagement - */ - protected $groupPriceManagement; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $productRepositoryMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $productMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $priceFactoryMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $groupServiceMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $priceModifierMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $websiteMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $configMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $storeManagerMock; - - protected function setUp() - { - $this->productRepositoryMock = $this->getMock( - '\Magento\Catalog\Model\ProductRepository', - [], - [], - '', - false - ); - $this->priceFactoryMock = $this->getMock( - 'Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory', - ['create'], - [], - '', - false - ); - $this->storeManagerMock = $this->getMockBuilder('\Magento\Store\Model\StoreManagerInterface') - ->setMethods(['getWebsite']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->groupServiceMock = $this->getMock('\Magento\Customer\Api\GroupRepositoryInterface'); - - $this->priceModifierMock = - $this->getMock('Magento\Catalog\Model\Product\PriceModifier', [], [], '', false); - $this->websiteMock = - $this->getMock('Magento\Store\Model\Website', ['getId', '__wakeup'], [], '', false); - $this->productMock = $this->getMock( - 'Magento\Catalog\Model\Product', - ['getData', 'setData', 'validate', 'save', 'getIdBySku', 'load', '__wakeup'], - [], - '', - false - ); - $this->websiteMock = - $this->getMock('Magento\Store\Model\Website', ['getId', '__wakeup'], [], '', false); - $this->productRepositoryMock->expects($this->any())->method('get')->with('product_sku') - ->will($this->returnValue($this->productMock)); - $this->configMock = $this->getMock('Magento\Framework\App\Config\ScopeConfigInterface'); - $this->storeManagerMock = $this->getMock('Magento\Store\Model\StoreManagerInterface'); - $this->groupPriceManagement = new GroupPriceManagement( - $this->productRepositoryMock, - $this->priceFactoryMock, - $this->groupServiceMock, - $this->priceModifierMock, - $this->configMock, - $this->storeManagerMock - ); - } - - /** - * @param string $configValue - * @param array $groupData - * @param array $expected - * @dataProvider getListDataProvider - */ - public function testGetList($configValue, $groupData, $expected) - { - $this->productRepositoryMock->expects($this->once())->method('get')->with('product_sku', true) - ->will($this->returnValue($this->productMock)); - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue([$groupData])); - $this->configMock - ->expects($this->once()) - ->method('getValue') - ->with('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE) - ->will($this->returnValue($configValue)); - $priceMock = $this->getMock('\Magento\Catalog\Api\Data\ProductGroupPriceInterface'); - $priceMock->expects($this->once()) - ->method('setCustomerGroupId') - ->with($expected['customer_group_id']) - ->willReturnSelf(); - $priceMock->expects($this->once()) - ->method('setValue') - ->with($expected['value']) - ->willReturnSelf(); - $this->priceFactoryMock - ->expects($this->once()) - ->method('create') - ->will($this->returnValue($priceMock)); - $prices = $this->groupPriceManagement->getList('product_sku'); - $this->assertCount(1, $prices); - $this->assertEquals($priceMock, $prices[0]); - } - - public function getListDataProvider() - { - return [ - [ - 1, - ['website_price' => 10, 'price' => 5, 'all_groups' => 1], - ['customer_group_id' => 'all', 'value' => 10] - ], - [ - 0, - ['website_price' => 10, 'price' => 5, 'all_groups' => 0, 'cust_group' => 1], - ['customer_group_id' => 1, 'value' => 5] - ] - ]; - } - - public function testSuccessRemoveGroupPrice() - { - $this->storeManagerMock - ->expects($this->never()) - ->method('getWebsite'); - $this->configMock - ->expects($this->once()) - ->method('getValue') - ->with('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE) - ->will($this->returnValue(0)); - $this->priceModifierMock->expects($this->once())->method('removeGroupPrice')->with($this->productMock, 4, 0); - - $this->assertEquals(true, $this->groupPriceManagement->remove('product_sku', 4)); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage No such entity. - */ - public function testRemoveGroupPriceFromNonExistingProduct() - { - $this->productRepositoryMock->expects($this->once())->method('get') - ->will($this->throwException(new NoSuchEntityException())); - $this->priceModifierMock->expects($this->never())->method('removeGroupPrice'); - $this->storeManagerMock - ->expects($this->never()) - ->method('getWebsite'); - $this->groupPriceManagement->remove('product_sku', null, 10); - } - - public function testSuccessRemoveGroupPriceFromWebsiteLevel() - { - $this->storeManagerMock - ->expects($this->once()) - ->method('getWebsite') - ->will($this->returnValue($this->websiteMock)); - $this->websiteMock->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->configMock - ->expects($this->once()) - ->method('getValue') - ->with('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE) - ->will($this->returnValue(1)); - $this->priceModifierMock->expects($this->once())->method('removeGroupPrice')->with($this->productMock, 4, 1); - - $this->assertEquals(true, $this->groupPriceManagement->remove('product_sku', 4)); - } - - public function testSetNewPriceWithGlobalPriceScope() - { - $group = $this->getMock('\Magento\Customer\Model\Data\Group', [], [], '', false); - $group->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->groupServiceMock->expects($this->once())->method('getById')->will($this->returnValue($group)); - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue([['cust_group' => 2, 'website_id' => 0, 'price' => 50]])); - $this->configMock - ->expects($this->once()) - ->method('getValue') - ->with('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE) - ->will($this->returnValue(1)); - - $this->productMock->expects($this->once())->method('setData')->with( - 'group_price', - [ - ['cust_group' => 2, 'website_id' => 0, 'price' => 50], - ['cust_group' => 1, 'website_id' => 0, 'price' => 100] - ] - ); - - $this->storeManagerMock->expects($this->once())->method('getWebsite') - ->will($this->returnValue($this->websiteMock)); - $this->websiteMock->expects($this->once())->method('getId') - ->will($this->returnValue(0)); - - $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock); - $this->groupPriceManagement->add('product_sku', 1, 100); - } - - public function testSetUpdatedPriceWithGlobalPriceScope() - { - $group = $this->getMock('\Magento\Customer\Model\Data\Group', [], [], '', false); - $this->groupServiceMock->expects($this->once())->method('getById')->will($this->returnValue($group)); - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue([['cust_group' => 2, 'website_id' => 0, 'price' => 50]])); - $this->configMock - ->expects($this->once()) - ->method('getValue') - ->with('catalog/price/scope', \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITE) - ->will($this->returnValue(0)); - - $this->productMock->expects($this->once())->method('setData')->with( - 'group_price', - [ - ['cust_group' => 2, 'website_id' => 0, 'price' => 100], - ] - ); - $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock); - $this->groupPriceManagement->add('product_sku', 2, 100); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage Values of following attributes are invalid: attr1, attr2 - */ - public function testSetThrowsExceptionIfDoesntValidate() - { - $group = $this->getMock('\Magento\Customer\Model\Data\Group', [], [], '', false); - $group->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue([])); - - $this->groupServiceMock->expects($this->once())->method('getById')->will($this->returnValue($group)); - $this->productMock->expects($this->once())->method('validate')->will( - $this->returnValue( - ['attr1' => '', 'attr2' => ''] - ) - ); - $this->productRepositoryMock->expects($this->never())->method('save'); - $this->groupPriceManagement->add('product_sku', 2, 100); - } - - /** - * @expectedException \Magento\Framework\Exception\CouldNotSaveException - */ - public function testSetThrowsExceptionIfCantSave() - { - $group = $this->getMock('\Magento\Customer\Model\Data\Group', [], [], '', false); - $group->expects($this->once())->method('getId')->will($this->returnValue(1)); - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue([])); - - $this->groupServiceMock->expects($this->once())->method('getById')->will($this->returnValue($group)); - $this->productRepositoryMock->expects($this->once()) - ->method('save') - ->will($this->throwException(new \Exception())); - $this->groupPriceManagement->add('product_sku', 2, 100); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - */ - public function testAddWithInvalidData() - { - $this->groupPriceManagement->add('product_sku', 1, '10string'); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/LinkTest.php index e081f7229468c27f54c578499ccb38e6a1c1f44f..29c855255c00b79cf413785d3aac401e694c435b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/LinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/LinkTest.php @@ -19,6 +19,16 @@ class LinkTest extends \PHPUnit_Framework_TestCase */ protected $resource; + /** + * @var \Magento\CatalogInventory\Helper\Stock|\PHPUnit_Framework_MockObject_MockObject + */ + protected $stockHelperMock; + + /** + * @var \Magento\Catalog\Model\Resource\Product\Link\Product\Collection|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productCollection; + protected function setUp() { $linkCollection = $this->getMockBuilder( @@ -35,12 +45,12 @@ class LinkTest extends \PHPUnit_Framework_TestCase $linkCollectionFactory->expects($this->any()) ->method('create') ->will($this->returnValue($linkCollection)); - $productCollection = $this->getMockBuilder( + $this->productCollection = $this->getMockBuilder( 'Magento\Catalog\Model\Resource\Product\Link\Product\Collection' )->disableOriginalConstructor()->setMethods( ['setLinkModel'] )->getMock(); - $productCollection->expects($this->any())->method('setLinkModel')->will($this->returnSelf()); + $this->productCollection->expects($this->any())->method('setLinkModel')->will($this->returnSelf()); $productCollectionFactory = $this->getMockBuilder( 'Magento\Catalog\Model\Resource\Product\Link\Product\CollectionFactory' )->disableOriginalConstructor()->setMethods( @@ -48,7 +58,7 @@ class LinkTest extends \PHPUnit_Framework_TestCase )->getMock(); $productCollectionFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($productCollection)); + ->will($this->returnValue($this->productCollection)); $this->resource = $this->getMock( 'Magento\Framework\Model\Resource\AbstractResource', @@ -63,11 +73,19 @@ class LinkTest extends \PHPUnit_Framework_TestCase ] ); + $this->stockHelperMock = $this->getMockBuilder('Magento\CatalogInventory\Helper\Stock') + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectManager->getObject( 'Magento\Catalog\Model\Product\Link', - ['linkCollectionFactory' => $linkCollectionFactory, 'productCollectionFactory' => $productCollectionFactory, - 'resource' => $this->resource] + [ + 'linkCollectionFactory' => $linkCollectionFactory, + 'productCollectionFactory' => $productCollectionFactory, + 'resource' => $this->resource, + 'stockHelper' => $this->stockHelperMock + ] ); } @@ -109,6 +127,10 @@ class LinkTest extends \PHPUnit_Framework_TestCase public function testGetProductCollection() { + $this->stockHelperMock + ->expects($this->once()) + ->method('addInStockFilterToCollection') + ->with($this->productCollection); $this->assertInstanceOf( 'Magento\Catalog\Model\Resource\Product\Link\Product\Collection', $this->model->getProductCollection() diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php index fe3ddb73eef8f821a2408b0feeb683677dc98f88..b5faf10b921badc5bd0c191581a1825cd351da31 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php @@ -61,51 +61,6 @@ class PriceModifierTest extends \PHPUnit_Framework_TestCase ]; } - public function testSuccessfullyRemoveGroupPriceSpecifiedForOneGroup() - { - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue($this->prices)); - $expectedPrices = [1 => $this->prices[1]]; - $this->productMock->expects($this->once())->method('setData')->with('group_price', $expectedPrices); - $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock); - $this->priceModifier->removeGroupPrice($this->productMock, 1, 1); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedMessage This product doesn't have group price - */ - public function testRemoveWhenGroupPricesNotExists() - { - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue([])); - $this->productMock->expects($this->never())->method('setData'); - $this->productRepositoryMock->expects($this->never())->method('save'); - $this->priceModifier->removeGroupPrice($this->productMock, 1, 1); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedMessage For current customerGroupId = '10' any group price exist'. - */ - public function testRemoveGroupPriceForNonExistingCustomerGroup() - { - $this->productMock - ->expects($this->once()) - ->method('getData') - ->with('group_price') - ->will($this->returnValue($this->prices)); - $this->productMock->expects($this->never())->method('setData'); - $this->productRepositoryMock->expects($this->never())->method('save'); - $this->priceModifier->removeGroupPrice($this->productMock, 10, 1); - } - /** * @expectedException \Magento\Framework\Exception\NoSuchEntityException * @expectedMessage This product doesn't have tier price diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php index 7ebf31ca906991674a8626f7bad2facb2e00a696..30d57127245e41d496498900252c317ce6e512a6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php @@ -16,7 +16,6 @@ use Magento\Customer\Model\GroupManagement; */ class PriceTest extends \PHPUnit_Framework_TestCase { - const KEY_GROUP_PRICE = 'group_price'; const KEY_TIER_PRICE = 'tier_price'; const PRICE_SCOPE_GLOBAL = 0; const PRICE_SCOPE_WEBSITE = 1; @@ -36,11 +35,6 @@ class PriceTest extends \PHPUnit_Framework_TestCase */ protected $product; - /** - * @var \Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $gpFactory; - /** * @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ @@ -66,16 +60,6 @@ class PriceTest extends \PHPUnit_Framework_TestCase $this->objectManagerHelper = new ObjectManagerHelper($this); $this->product = $this->objectManagerHelper->getObject('Magento\Catalog\Model\Product'); - $this->gpFactory = $this->getMockForAbstractClass( - 'Magento\Catalog\Api\Data\ProductGroupPriceInterfaceFactory', - [], - '', - false, - true, - true, - ['create'] - ); - $this->tpFactory = $this->getMockForAbstractClass( 'Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory', [], @@ -125,7 +109,6 @@ class PriceTest extends \PHPUnit_Framework_TestCase $this->model = $this->objectManagerHelper->getObject( 'Magento\Catalog\Model\Product\Type\Price', [ - 'groupPriceFactory' => $this->gpFactory, 'tierPriceFactory' => $this->tpFactory, 'config' => $this->scopeConfigMock, 'storeManager' => $storeMangerMock, @@ -135,7 +118,6 @@ class PriceTest extends \PHPUnit_Framework_TestCase } /** - * testGetGroupPricesWithNull * testGetTierPricesWithNull * * @dataProvider nullPricesDataProvider @@ -157,86 +139,10 @@ class PriceTest extends \PHPUnit_Framework_TestCase public function nullPricesDataProvider() { return [ - 'testGetGroupPricesWithNull' => [$this::KEY_GROUP_PRICE, 'setGroupPrices'], 'testGetTierPricesWithNull' => [$this::KEY_TIER_PRICE, 'setTierPrices'] ]; } - /** - * testGetGroupPrices - * testSetGroupPrices - * - * @dataProvider pricesDataProvider - */ - public function testGroupPrices($priceScope, $expectedWebsiteId) - { - // establish the behavior of the mocks - $this->scopeConfigMock->expects($this->any()) - ->method('getValue') - ->will($this->returnValue($priceScope)); - $this->websiteMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue($expectedWebsiteId)); - $this->gpFactory->expects($this->any()) - ->method('create') - ->will($this->returnCallback(function () { - return $this->objectManagerHelper->getObject('Magento\Catalog\Model\Product\GroupPrice'); - })); - - // create sample GroupPrice objects that would be coming from a REST call - $gp1 = $this->objectManagerHelper->getObject('Magento\Catalog\Model\Product\GroupPrice'); - $gp1->setValue(10); - $gp1->setCustomerGroupId(1); - $gp2 = $this->objectManagerHelper->getObject('Magento\Catalog\Model\Product\GroupPrice'); - $gp2->setValue(20); - $gp2->setCustomerGroupId(2); - $gps = [$gp1, $gp2]; - - // force the product to have null group prices - $this->product->setData($this::KEY_GROUP_PRICE, null); - $this->assertNull($this->product->getData($this::KEY_GROUP_PRICE)); - - // set the product with the GroupPrice objects - $this->model->setGroupPrices($this->product, $gps); - - // test the data actually set on the product - $gpArray = $this->product->getData($this::KEY_GROUP_PRICE); - $this->assertNotNull($gpArray); - $this->assertTrue(is_array($gpArray)); - $this->assertEquals(sizeof($gps), sizeof($gpArray)); - - for ($i = 0; $i < sizeof($gps); $i++) { - $gpData = $gpArray[$i]; - $this->assertEquals($expectedWebsiteId, $gpData['website_id'], 'Website Id does not match'); - $this->assertEquals($gps[$i]->getValue(), $gpData['price'], 'Price/Value does not match'); - $this->assertEquals($gps[$i]->getValue(), $gpData['website_price'], 'WebsitePrice/Value does not match'); - $this->assertEquals( - $gps[$i]->getCustomerGroupId(), - $gpData['cust_group'], - 'Customer group Id does not match' - ); - } - - // test with the data retrieved as a REST object - $gpRests = $this->model->getGroupPrices($this->product); - $this->assertNotNull($gpRests); - $this->assertTrue(is_array($gpRests)); - $this->assertEquals(sizeof($gps), sizeof($gpRests)); - - for ($i = 0; $i < sizeof($gps); $i++) { - $this->assertEquals( - $gps[$i]->getValue(), - $gpRests[$i]->getValue(), - 'REST: Price/Value does not match' - ); - $this->assertEquals( - $gps[$i]->getCustomerGroupId(), - $gpRests[$i]->getCustomerGroupId(), - 'REST: Customer group Id does not match' - ); - } - } - /** * @return array */ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/TypeTransitionManagerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/TypeTransitionManagerTest.php index f33b89d1c3b673683acc324c1e9d6fcc385b3cfe..b1c144471871337341ca49cefd7e3808ce99b52d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/TypeTransitionManagerTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/TypeTransitionManagerTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Catalog\Test\Unit\Model\Product; -use \Magento\Catalog\Model\Product\TypeTransitionManager; - class TypeTransitionManagerTest extends \PHPUnit_Framework_TestCase { /** @@ -19,38 +17,62 @@ class TypeTransitionManagerTest extends \PHPUnit_Framework_TestCase */ protected $productMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $weightResolver; + protected function setUp() { - $this->model = new TypeTransitionManager( - [ - 'simple' => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, - 'virtual' => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, - ] - ); + if (version_compare('5.5.28', phpversion(), '=')) { + $this->markTestSkipped('MAGETWO-43290: This test fails with Segmentation fault on PHP 5.5.28'); + } $this->productMock = $this->getMock( 'Magento\Catalog\Model\Product', - ['hasIsVirtual', 'getTypeId', 'setTypeId', 'setTypeInstance', '__wakeup'], + ['getTypeId', 'setTypeId', 'setTypeInstance'], [], '', false ); + $this->weightResolver = $this->getMock('Magento\Catalog\Model\Product\Edit\WeightResolver'); + $this->model = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this)) + ->getObject( + 'Magento\Catalog\Model\Product\TypeTransitionManager', + [ + 'weightResolver' => $this->weightResolver, + 'compatibleTypes' => [ + 'simple' => \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, + 'virtual' => \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, + ] + ] + ); } /** - * @param bool $isVirtual + * @param bool $hasWeight * @param string $currentTypeId * @param string $expectedTypeId * @dataProvider processProductDataProvider */ - public function testProcessProduct($isVirtual, $currentTypeId, $expectedTypeId) + public function testProcessProduct($hasWeight, $currentTypeId, $expectedTypeId) { - $this->productMock->expects($this->any())->method('hasIsVirtual')->will($this->returnValue($isVirtual)); $this->productMock->expects($this->once())->method('getTypeId')->will($this->returnValue($currentTypeId)); $this->productMock->expects($this->once())->method('setTypeInstance')->with(null); + $this->weightResolver->expects($this->once())->method('resolveProductHasWeight')->willReturn($hasWeight); $this->productMock->expects($this->once())->method('setTypeId')->with($expectedTypeId); $this->model->processProduct($this->productMock); } + /** + * @return void + */ + public function testProcessProductWithWrongTypeId() + { + $this->productMock->expects($this->once())->method('getTypeId')->will($this->returnValue('wrong-type')); + $this->weightResolver->expects($this->never())->method('resolveProductHasWeight'); + $this->model->processProduct($this->productMock); + } + /** * @return array */ @@ -58,24 +80,24 @@ class TypeTransitionManagerTest extends \PHPUnit_Framework_TestCase { return [ [ - false, + true, \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, ], [ - false, + true, + \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, - \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE ], [ - true, + false, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, - \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL + \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, ], [ - true, + false, + \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, - \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL ] ]; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php index 473d3605648af8157546d6beb61c10127d52c331..e7bc9cf40adccbd0e80565d8f8c3b827fbbb2eb3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/UrlTest.php @@ -51,12 +51,6 @@ class UrlTest extends \PHPUnit_Framework_TestCase 'Magento\UrlRewrite\Model\UrlFinderInterface' )->disableOriginalConstructor()->getMock(); - $this->catalogCategory = $this->getMockBuilder( - 'Magento\Catalog\Helper\Category' - )->disableOriginalConstructor()->setMethods( - ['getCategoryUrlPath'] - )->getMock(); - $this->url = $this->getMockBuilder( 'Magento\Framework\Url' )->disableOriginalConstructor()->setMethods( @@ -70,6 +64,12 @@ class UrlTest extends \PHPUnit_Framework_TestCase $storeManager = $this->getMockForAbstractClass('Magento\Store\Model\StoreManagerInterface'); $storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store)); + $urlFactory = $this->getMockBuilder('\Magento\Framework\UrlFactory') + ->disableOriginalConstructor() + ->getMock(); + $urlFactory->method('create') + ->willReturn($this->url); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectManager->getObject( 'Magento\Catalog\Model\Product\Url', @@ -77,7 +77,7 @@ class UrlTest extends \PHPUnit_Framework_TestCase 'filter' => $this->filter, 'catalogCategory' => $this->catalogCategory, 'storeManager' => $storeManager, - 'url' => $this->url, + 'urlFactory' => $urlFactory, 'sidResolver' => $this->sidResolver, ] ); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index 261296b1d3c3ee748650df3566fa469b42b9902b..8d11bf4a51dcd9827c942b2e8fb789fe25aabe38 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -1054,6 +1054,7 @@ class ProductRepositoryTest extends \PHPUnit_Framework_TestCase ImageContentInterface::TYPE => 'image/jpeg', ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', ], + 'media_type' => 'media_type', ], ]; @@ -1116,6 +1117,7 @@ class ProductRepositoryTest extends \PHPUnit_Framework_TestCase 'label' => 'label_text', 'position' => 10, 'disabled' => false, + 'media_type' => 'media_type', ] ); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 97f42ea1d09ab40dba23ae24684f516fb520b74d..821ff62d8d900288617fbf6ad6448c3215da69d4 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -153,6 +153,16 @@ class ProductTest extends \PHPUnit_Framework_TestCase */ protected $linkTypeProviderMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $mediaGalleryEntryConverterPoolMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $converterMock; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -307,6 +317,28 @@ class ProductTest extends \PHPUnit_Framework_TestCase $this->entityCollectionProviderMock = $this->getMock('Magento\Catalog\Model\ProductLink\CollectionProvider', ['getCollection'], [], '', false); + $this->mediaGalleryEntryConverterPoolMock = + $this->getMock( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool', + ['getConverterByMediaType'], + [], + '', + false + ); + + $this->converterMock = + $this->getMock( + '\Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter', + [], + [], + '', + false + ); + + $this->mediaGalleryEntryConverterPoolMock->expects($this->any())->method('getConverterByMediaType')->willReturn( + $this->converterMock + ); + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->model = $this->objectManagerHelper->getObject( 'Magento\Catalog\Model\Product', @@ -332,6 +364,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase 'customAttributeFactory' => $this->attributeValueFactory, 'entityCollectionProvider' => $this->entityCollectionProviderMock, 'linkTypeProvider' => $this->linkTypeProviderMock, + 'mediaGalleryEntryConverterPool' => $this->mediaGalleryEntryConverterPoolMock, 'data' => ['id' => 1] ] ); @@ -1067,53 +1100,26 @@ class ProductTest extends \PHPUnit_Framework_TestCase [ 'value_id' => 1, 'file' => 'imageFile.jpg', + 'media_type' => 'image', ], [ 'value_id' => 2, 'file' => 'smallImageFile.jpg', + 'media_type' => 'image', ], ] ]; $this->model->setData('media_gallery', $mediaEntries); - $entry1 = $this->getMockBuilder('\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface') - ->setMethods(['setId']) - ->getMockForAbstractClass(); - $entry1->expects($this->once())->method('setId')->with(1); - $entry2 = $this->getMockBuilder('\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface') - ->setMethods(['setId']) - ->getMockForAbstractClass(); - $entry2->expects($this->once())->method('setId')->with(2); - - $this->mediaGalleryEntryFactoryMock->expects($this->at(0)) - ->method('create') - ->willReturn($entry1); - $this->mediaGalleryEntryFactoryMock->expects($this->at(1)) - ->method('create') - ->willReturn($entry2); + $entry1 = + $this->getMock('\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface', [], [], '', false); + $entry2 = + $this->getMock('\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface', [], [], '', false); - $this->dataObjectHelperMock->expects($this->at(0)) - ->method('populateWithArray') - ->with( - $entry1, - [ - 'value_id' => 1, - 'file' => 'imageFile.jpg', - 'types' => ['image'], - ], - '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface' - ); - $this->dataObjectHelperMock->expects($this->at(1)) - ->method('populateWithArray') - ->with( - $entry1, - [ - 'value_id' => 2, - 'file' => 'smallImageFile.jpg', - 'types' => ['small_image'], - ], - '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface' - ); + $this->converterMock->expects($this->exactly(2))->method('convertTo')->willReturnOnConsecutiveCalls( + $entry1, + $entry2 + ); $this->assertEquals([$entry1, $entry2], $this->model->getMediaGalleryEntries()); } @@ -1135,40 +1141,45 @@ class ProductTest extends \PHPUnit_Framework_TestCase ImageContentInterface::TYPE => 'image/jpg', ImageContentInterface::BASE64_ENCODED_DATA => 'content_data' ] - ] + ], + 'media_type' => 'image' ] ], ]; - $contentMock = - $this->getMockBuilder('\Magento\Framework\Api\Data\ImageContentInterface') - ->setMethods(['getBase64EncodedData', 'getType', 'getName']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $contentMock->expects($this->once())->method('getBase64EncodedData') - ->willReturn($expectedResult['images'][0]['content']['data']['base64_encoded_data']); - $contentMock->expects($this->once())->method('getType') - ->willReturn($expectedResult['images'][0]['content']['data']['type']); - $contentMock->expects($this->once())->method('getName') - ->willReturn($expectedResult['images'][0]['content']['data']['name']); - $entryMock = $this->getMockBuilder('\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface') - ->setMethods(['getId', 'getFile', 'getLabel', 'getPosition', 'isDisabled', 'types', 'getContent']) - ->getMockForAbstractClass(); - $entryMock->expects($this->once())->method('getId') - ->willReturn($expectedResult['images'][0]['value_id']); - $entryMock->expects($this->once())->method('getFile') - ->willReturn($expectedResult['images'][0]['file']); - $entryMock->expects($this->once())->method('getLabel') - ->willReturn($expectedResult['images'][0]['label']); - $entryMock->expects($this->once())->method('getPosition') - ->willReturn($expectedResult['images'][0]['position']); - $entryMock->expects($this->once())->method('isDisabled') - ->willReturn($expectedResult['images'][0]['disabled']); - $entryMock->expects($this->once())->method('getTypes') - ->willReturn($expectedResult['images'][0]['types']); - $entryMock->expects($this->once())->method('getContent') - ->willReturn($contentMock); + ->setMethods( + [ + 'getId', + 'getFile', + 'getLabel', + 'getPosition', + 'isDisabled', + 'types', + 'getContent', + 'getMediaType' + ] + ) + ->getMockForAbstractClass(); + + $result = [ + "value_id" => 1, + 'file' => 'file1.jpg', + 'label' => 'label_text', + 'position' => 4, + 'disabled' => false, + 'types' => ['image'], + 'content' => [ + 'data' => [ + ImageContentInterface::NAME => 'product_image', + ImageContentInterface::TYPE => 'image/jpg', + ImageContentInterface::BASE64_ENCODED_DATA => 'content_data' + ] + ], + 'media_type' => 'image' + ]; + + $this->converterMock->expects($this->once())->method('convertFrom')->with($entryMock)->willReturn($result); $this->model->setMediaGalleryEntries([$entryMock]); $this->assertEquals($expectedResult, $this->model->getMediaGallery()); @@ -1213,56 +1224,6 @@ class ProductTest extends \PHPUnit_Framework_TestCase $this->assertEquals("blue", $this->model->getCustomAttribute($colorAttributeCode)->getValue()); } - /** - * @dataProvider priceDataProvider - */ - public function testGetGroupPrices($originalGroupPrices) - { - $this->invokeGetGroupOrTierPrices($originalGroupPrices, 'getGroupPrices'); - } - - /** - * @dataProvider priceDataProvider - */ - public function testGetTierPrices($originalGroupPrices) - { - $this->invokeGetGroupOrTierPrices($originalGroupPrices, 'getTierPrices'); - } - - protected function invokeGetGroupOrTierPrices($originalPrices, $getter) - { - // the priceModel's getter method will return the originalPrices - $priceModelMock = $this->getMockBuilder('Magento\Catalog\Model\Product\Type\Price') - ->disableOriginalConstructor() - ->setMethods([$getter]) - ->getMock(); - $priceModelMock->expects($this->any()) - ->method($getter) - ->will($this->returnValue($originalPrices)); - - // the catalogProductType's priceFactory method will return the above priceModel - $catalogProductTypeMock = $this->getMockBuilder('Magento\Catalog\Model\Product\Type') - ->disableOriginalConstructor() - ->setMethods(['priceFactory']) - ->getMock(); - $catalogProductTypeMock->expects(($this->any())) - ->method('priceFactory') - ->will($this->returnValue($priceModelMock)); - - // the productModel - $productModel = $this->objectManagerHelper->getObject( - 'Magento\Catalog\Model\Product', - [ - 'catalogProductType' => $catalogProductTypeMock - ] - ); - - $expectedResultIsEmpty = (empty($originalPrices) ? true : false); - $groupPrices = $productModel->$getter(); - $actualResultIsEmpty = (empty($groupPrices) ? true : false); - $this->assertEquals($expectedResultIsEmpty, $actualResultIsEmpty); - } - /** * @return array */ @@ -1383,7 +1344,7 @@ class ProductTest extends \PHPUnit_Framework_TestCase '', false ); - + $productTypePriceMock->expects($this->any()) ->method('getFinalPrice') ->with($qty, $this->model) @@ -1407,4 +1368,19 @@ class ProductTest extends \PHPUnit_Framework_TestCase $this->productTypeInstanceMock->expects($this->never())->method('priceFactory'); $this->assertEquals($finalPrice, $this->model->getFinalPrice($qty)); } + + public function testGetTypeId() + { + $productType = $this->getMockBuilder('Magento\Catalog\Model\Product\Type\Virtual') + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->productTypeInstanceMock->expects($this->exactly(2))->method('factory')->will( + $this->returnValue($productType) + ); + + $this->model->getTypeInstance(); + $this->model->setTypeId('typeId'); + $this->model->getTypeInstance(); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Resource/Product/Attribute/Backend/MediaTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Resource/Product/Attribute/Backend/MediaTest.php index a781f95d8b19261d23f95f7ba1c54ddb4d101bb9..cdb39b1984e0c599b2f8d3dc658f46494a4240fc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Resource/Product/Attribute/Backend/MediaTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Resource/Product/Attribute/Backend/MediaTest.php @@ -45,14 +45,27 @@ class MediaTest extends \PHPUnit_Framework_TestCase */ protected $attribute; - protected function setUp() + /** + * @var array + */ + protected $fields = [ + 'value_id' => ['DATA_TYPE' => 'int', 'NULLABLE' => false], + 'store_id' => ['DATA_TYPE' => 'int', 'NULLABLE' => false], + 'provider' => ['DATA_TYPE' => 'varchar', 'NULLABLE' => true], + 'url' => ['DATA_TYPE' => 'text', 'NULLABLE' => true], + 'title' => ['DATA_TYPE' => 'varchar', 'NULLABLE' => true], + 'description' => ['DATA_TYPE' => 'text', 'NULLABLE' => true], + 'metadata' => ['DATA_TYPE' => 'text', 'NULLABLE' => true], + ]; + + public function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->connection = $this->getMock('Magento\Framework\DB\Adapter\AdapterInterface', [], [], '', false); + $this->connection = $this->getMock('Magento\Framework\DB\Adapter\Pdo\Mysql', [], [], '', false); $resource = $this->getMock('Magento\Framework\App\Resource', [], [], '', false); $resource->expects($this->any()) - ->method('getConnection') - ->willReturn($this->connection); + ->method('getConnection') + ->willReturn($this->connection); $resource->expects($this->any())->method('getTableName')->willReturn('table'); $this->connection->expects($this->any())->method('setCacheAdapter'); $this->resource = $objectManager->getObject( @@ -65,13 +78,216 @@ class MediaTest extends \PHPUnit_Framework_TestCase $this->attribute = $this->getMock('Magento\Eav\Model\Entity\Attribute\AbstractAttribute', [], [], '', false); } + public function testLoadDataFromTableByValueId() + { + $tableNameAlias = 'catalog_product_entity_media_gallery_value_video'; + $ids = [5, 8]; + $storeId = 0; + $cols = [ + 'value_id' => 'value_id', + 'video_provider_default' => 'provider', + 'video_url_default' => 'url', + 'video_title_default' => 'title', + 'video_description_default' => 'description', + 'video_metadata_default' => 'metadata', + ]; + $leftJoinTables = [ + 0 => [ + 0 => + [ + 'store_value' => 'catalog_product_entity_media_gallery_value_video', + ], + 1 => 'main.value_id = store_value.value_id AND store_value.store_id = 0', + 2 => + [ + 'video_provider' => 'provider', + 'video_url' => 'url', + 'video_title' => 'title', + 'video_description' => 'description', + 'video_metadata' => 'metadata', + ], + ], + ]; + $whereCondition = null; + $getTableReturnValue = 'table'; + $this->connection->expects($this->once())->method('select')->will($this->returnValue($this->select)); + $this->select->expects($this->at(0))->method('from')->with( + [ + 'main' => $getTableReturnValue, + ], + [ + 'value_id' => 'value_id', + 'video_provider_default' => 'provider', + 'video_url_default' => 'url', + 'video_title_default' => 'title', + 'video_description_default' => 'description', + 'video_metadata_default' => 'metadata', + ] + )->willReturnSelf(); + $this->select->expects($this->at(1))->method('where')->with( + 'main.value_id IN(?)', + $ids + )->willReturnSelf(); + $this->select->expects($this->at(2))->method('where')->with( + 'main.store_id = ?', + $storeId + )->willReturnSelf(); + $resultRow = [ + [ + 'value_id' => '4', + 'store_id' => 1, + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title_default' => 'Some first title', + 'video_description_default' => 'Description first', + 'video_metadata_default' => 'meta one', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => 'Some first title', + 'video_description' => 'Description first', + 'video_metadata' => 'meta one', + ], + [ + 'value_id' => '5', + 'store_id' => 0, + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title_default' => 'Some second title', + 'video_description_default' => 'Description second', + 'video_metadata_default' => 'meta two', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => '', + ] + ]; + $this->connection->expects($this->once())->method('fetchAll') + ->with($this->select) + ->willReturn($resultRow); + + $methodResult = $this->resource->loadDataFromTableByValueId( + $tableNameAlias, + $ids, + $storeId, + $cols, + $leftJoinTables, + $whereCondition + ); + $this->assertEquals($resultRow, $methodResult); + } + + public function testLoadDataFromTableByValueIdNoColsWithWhere() + { + $tableNameAlias = 'catalog_product_entity_media_gallery_value_video'; + $ids = [5, 8]; + $storeId = 0; + $cols = null; + $leftJoinTables = [ + 0 => + [ + 0 => + [ + 'store_value' => 'catalog_product_entity_media_gallery_value_video', + ], + 1 => 'main.value_id = store_value.value_id AND store_value.store_id = 0', + 2 => + [ + 'video_provider' => 'provider', + 'video_url' => 'url', + 'video_title' => 'title', + 'video_description' => 'description', + 'video_metadata' => 'metadata', + ], + ], + ]; + $whereCondition = 'main.store_id = ' . $storeId; + $getTableReturnValue = 'table'; + + $this->connection->expects($this->once())->method('select')->will($this->returnValue($this->select)); + $this->select->expects($this->at(0))->method('from')->with( + [ + 'main' => $getTableReturnValue, + ], + '*' + )->willReturnSelf(); + + $this->select->expects($this->at(1))->method('where')->with( + 'main.value_id IN(?)', + $ids + )->willReturnSelf(); + + $this->select->expects($this->at(2))->method('where')->with( + 'main.store_id = ?', + $storeId + )->willReturnSelf(); + + $this->select->expects($this->at(3))->method('where')->with( + $whereCondition + )->willReturnSelf(); + + $resultRow = [ + [ + 'value_id' => '4', + 'store_id' => 1, + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title_default' => 'Some first title', + 'video_description_default' => 'Description first', + 'video_metadata_default' => 'meta one', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => 'Some first title', + 'video_description' => 'Description first', + 'video_metadata' => 'meta one', + ], + [ + 'value_id' => '5', + 'store_id' => 0, + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title_default' => 'Some second title', + 'video_description_default' => 'Description second', + 'video_metadata_default' => 'meta two', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => '', + ] + ]; + + $this->connection->expects($this->once())->method('fetchAll') + ->with($this->select) + ->willReturn($resultRow); + + $methodResult = $this->resource->loadDataFromTableByValueId( + $tableNameAlias, + $ids, + $storeId, + $cols, + $leftJoinTables, + $whereCondition + ); + + $this->assertEquals($resultRow, $methodResult); + } + + public function testBindValueToEntityRecordExists() + { + $valueId = 14; + $entityId = 1; + $this->resource->bindValueToEntity($valueId, $entityId); + } + public function testLoadGallery() { $productId = 5; $storeId = 1; $attributeId = 6; $getTableReturnValue = 'table'; - $quoteInfoReturnValue = 'main.value_id = value.value_id AND value.store_id = ' . $storeId; + $quoteInfoReturnValue = + 'main.value_id = value.value_id AND value.store_id = ' . $storeId . ' AND value.entity_id = ' . $productId; $positionCheckSql = 'testchecksql'; $resultRow = [ [ @@ -98,48 +314,106 @@ class MediaTest extends \PHPUnit_Framework_TestCase ], [ 'value_id', - 'file' => 'value' + 'file' => 'value', + 'media_type' => 'media_type' ] )->willReturnSelf(); - - $this->product->expects($this->at(0))->method('getStoreId')->will($this->returnValue($storeId)); - $this->connection->expects($this->once())->method('quoteInto') - ->with('main.value_id = value.value_id AND value.store_id = ?', $storeId) - ->will($this->returnValue($quoteInfoReturnValue)); - $this->select->expects($this->at(1))->method('joinLeft')->with( + $this->select->expects($this->at(1))->method('joinInner')->with( + ['entity' => $getTableReturnValue], + 'main.value_id = entity.value_id', + ['entity_id' => 'entity_id'] + )->willReturnSelf(); + $this->product->expects($this->at(0))->method('getId')->willReturn($productId); + $this->product->expects($this->at(1))->method('getStoreId')->will($this->returnValue($storeId)); + $this->connection->expects($this->exactly(3))->method('quoteInto')->withConsecutive( + ['value.store_id = ?', 1], + ['value.entity_id = ?', 5], + ['default_value.entity_id = ?', 5] + )->willReturnOnConsecutiveCalls( + 'value.store_id = ' . $storeId, + 'value.entity_id = ' . $productId, + 'default_value.entity_id = ' . $productId + ); + $this->select->expects($this->at(2))->method('joinLeft')->with( ['value' => $getTableReturnValue], $quoteInfoReturnValue, [ 'label', 'position', 'disabled' - ])->willReturnSelf(); - $this->select->expects($this->at(2))->method('joinLeft')->with( + ] + )->willReturnSelf(); + $this->select->expects($this->at(3))->method('joinLeft')->with( ['default_value' => $getTableReturnValue], - 'main.value_id = default_value.value_id AND default_value.store_id = 0', + 'main.value_id = default_value.value_id AND default_value.store_id = 0 AND default_value.entity_id = ' + . $productId, ['label_default' => 'label', 'position_default' => 'position', 'disabled_default' => 'disabled'] )->willReturnSelf(); - $this->model->expects($this->at(0))->method('getAttribute')->will($this->returnValue($this->attribute)); - $this->attribute->expects($this->at(0))->method('getId')->will($this->returnValue($attributeId)); - $this->select->expects($this->at(3))->method('where')->with( + $this->select->expects($this->at(4))->method('where')->with( 'main.attribute_id = ?', $attributeId )->willReturnSelf(); - $this->product->expects($this->at(1))->method('getId')->willReturn($productId); - $this->select->expects($this->at(4))->method('where')->with( - 'main.entity_id = ?', - $productId - )->willReturnSelf(); - $this->select->expects($this->at(5))->method('where') - ->with($positionCheckSql . ' IS NOT NULL') - ->willReturnSelf(); + $this->select->expects($this->at(5))->method('where')->with('main.disabled = 0')->willReturnSelf(); + $this->select->expects($this->at(6))->method('where') + ->with('entity.entity_id = ?', $productId) + ->willReturnSelf(); $this->select->expects($this->once())->method('order') - ->with($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC) - ->willReturnSelf(); + ->with($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC) + ->willReturnSelf(); $this->connection->expects($this->once())->method('fetchAll') - ->with($this->select) - ->willReturn($resultRow); + ->with($this->select) + ->willReturn($resultRow); + + $this->assertEquals($resultRow, $this->resource->loadProductGalleryByAttributeId($this->product, $attributeId)); + } + + public function testInsertGalleryValueInStore() + { + $data = [ + 'value_id' => '8', + 'store_id' => 0, + 'provider' => '', + 'url' => 'https://www.youtube.com/watch?v=abcdfghijk', + 'title' => 'New Title', + 'description' => 'New Description', + 'metadata' => 'New metadata', + ]; + + $this->connection->expects($this->once())->method('describeTable')->willReturn($this->fields); + $this->connection->expects($this->any())->method('prepareColumnValue')->willReturnOnConsecutiveCalls( + '8', + 0, + '', + 'https://www.youtube.com/watch?v=abcdfghijk', + 'New Title', + 'New Description', + 'New metadata' + ); + + $this->resource->insertGalleryValueInStore($data); + } + + public function testDeleteGalleryValueInStore() + { + $valueId = 4; + $entityId = 6; + $storeId = 1; + + $this->connection->expects($this->exactly(3))->method('quoteInto')->withConsecutive( + ['value_id = ?', (int)$valueId], + ['entity_id = ?', (int)$entityId], + ['store_id = ?', (int)$storeId] + )->willReturnOnConsecutiveCalls( + 'value_id = ' . $valueId, + 'entity_id = ' . $entityId, + 'store_id = ' . $storeId + ); + + $this->connection->expects($this->once())->method('delete')->with( + 'table', + 'value_id = 4 AND entity_id = 6 AND store_id = 1' + )->willReturnSelf(); - $this->assertEquals($resultRow, $this->resource->loadGallery($this->product, $this->model)); + $this->resource->deleteGalleryValueInStore($valueId, $entityId, $storeId); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php index 234ae5b6429e9748be4a3ee246d0bc33ee497c23..d458c8c5a23a907df9e69ec481122418e864c026 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php @@ -39,9 +39,9 @@ class BasePriceTest extends \PHPUnit_Framework_TestCase protected $regularPriceMock; /** - * @var \Magento\Catalog\Pricing\Price\GroupPrice|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Pricing\Price\TierPrice|\PHPUnit_Framework_MockObject_MockObject */ - protected $groupPriceMock; + protected $tearPriceMock; /** * @var \Magento\Catalog\Pricing\Price\SpecialPrice|\PHPUnit_Framework_MockObject_MockObject @@ -62,7 +62,7 @@ class BasePriceTest extends \PHPUnit_Framework_TestCase $this->saleableItemMock = $this->getMock('Magento\Catalog\Model\Product', [], [], '', false); $this->priceInfoMock = $this->getMock('Magento\Framework\Pricing\PriceInfo\Base', [], [], '', false); $this->regularPriceMock = $this->getMock('Magento\Catalog\Pricing\Price\RegularPrice', [], [], '', false); - $this->groupPriceMock = $this->getMock('Magento\Catalog\Pricing\Price\GroupPrice', [], [], '', false); + $this->tearPriceMock = $this->getMock('Magento\Catalog\Pricing\Price\TierPrice', [], [], '', false); $this->specialPriceMock = $this->getMock('Magento\Catalog\Pricing\Price\SpecialPrice', [], [], '', false); $this->calculatorMock = $this->getMock('Magento\Framework\Pricing\Adjustment\Calculator', [], [], '', false); @@ -71,7 +71,7 @@ class BasePriceTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($this->priceInfoMock)); $this->prices = [ 'regular_price' => $this->regularPriceMock, - 'group_price' => $this->groupPriceMock, + 'tear_price' => $this->tearPriceMock, 'special_price' => $this->specialPriceMock, ]; @@ -98,7 +98,7 @@ class BasePriceTest extends \PHPUnit_Framework_TestCase $this->regularPriceMock->expects($this->exactly(3)) ->method('getValue') ->will($this->returnValue(100)); - $this->groupPriceMock->expects($this->exactly(2)) + $this->tearPriceMock->expects($this->exactly(2)) ->method('getValue') ->will($this->returnValue(99)); $this->specialPriceMock->expects($this->any()) diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/GroupPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/GroupPriceTest.php deleted file mode 100644 index 79be369b15188370ceab50fc9368d74d6ad5ca15..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/GroupPriceTest.php +++ /dev/null @@ -1,250 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -namespace Magento\Catalog\Test\Unit\Pricing\Price; - -/** - * Group price test - */ -class GroupPriceTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Magento\Catalog\Pricing\Price\GroupPrice - */ - protected $groupPrice; - - /** - * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject - */ - protected $productMock; - - /** - * @var \Magento\Catalog\Model\Resource\Product|\PHPUnit_Framework_MockObject_MockObject - */ - protected $productResourceMock; - - /** - * @var \Magento\Framework\Pricing\Adjustment\Calculator|\PHPUnit_Framework_MockObject_MockObject - */ - protected $calculatorMock; - - /** - * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerSessionMock; - - /** - * @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerMock; - - /** - * @var \Magento\Catalog\Model\Entity\Attribute|\PHPUnit_Framework_MockObject_MockObject - */ - protected $attributeMock; - - /** - * @var \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice|\PHPUnit_Framework_MockObject_MockObject - */ - protected $backendMock; - - /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $priceCurrencyMock; - - /** - * Set up test case - */ - public function setUp() - { - $this->productMock = $this->getMock( - 'Magento\Catalog\Model\Product', - ['__wakeup', 'getCustomerGroupId', 'getPriceInfo', 'getResource', 'getData'], - [], - '', - false - ); - $this->productResourceMock = $this->getMock( - 'Magento\Catalog\Model\Resource\Product', - [], - [], - '', - false - ); - $this->calculatorMock = $this->getMock( - 'Magento\Framework\Pricing\Adjustment\Calculator', - [], - [], - '', - false - ); - $this->customerSessionMock = $this->getMock( - 'Magento\Customer\Model\Session', - [], - [], - '', - false - ); - $this->customerMock = $this->getMock( - 'Magento\Customer\Model\Customer', - [], - [], - '', - false - ); - $this->attributeMock = $this->getMock( - 'Magento\Catalog\Model\Entity\Attribute', - [], - [], - '', - false - ); - $this->backendMock = $this->getMock( - 'Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice', - [], - [], - '', - false - ); - - $this->priceCurrencyMock = $this->getMock('\Magento\Framework\Pricing\PriceCurrencyInterface'); - - $this->groupPrice = new \Magento\Catalog\Pricing\Price\GroupPrice( - $this->productMock, - 1, - $this->calculatorMock, - $this->priceCurrencyMock, - $this->customerSessionMock - ); - } - - /** - * test get group price, customer group in session - */ - public function testGroupPriceCustomerGroupInSession() - { - $groupPrice = 80; - $convertedValue = 56.24; - $this->productMock->expects($this->once()) - ->method('getCustomerGroupId') - ->will($this->returnValue(null)); - $this->customerSessionMock->expects($this->once()) - ->method('getCustomerGroupId') - ->will($this->returnValue(3)); - $this->productMock->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($this->productResourceMock)); - $this->productResourceMock->expects($this->once()) - ->method('getAttribute') - ->with($this->equalTo('group_price')) - ->will($this->returnValue($this->attributeMock)); - $this->attributeMock->expects($this->once()) - ->method('getBackend') - ->will($this->returnValue($this->backendMock)); - $this->backendMock->expects($this->once()) - ->method('afterLoad') - ->with($this->equalTo($this->productMock)) - ->will($this->returnValue($this->backendMock)); - $this->productMock->expects($this->once()) - ->method('getData') - ->with( - $this->equalTo('group_price'), - $this->equalTo(null) - ) - ->will($this->returnValue( - [ - [ - 'cust_group' => 3, - 'website_price' => $groupPrice, - ], - ] - - )); - $this->priceCurrencyMock->expects($this->once()) - ->method('convertAndRound') - ->with($groupPrice) - ->will($this->returnValue($convertedValue)); - $this->assertEquals($convertedValue, $this->groupPrice->getValue()); - } - - /** - * test get group price, customer group in session - * - * @param int $customerGroup - * @dataProvider dataProviderGroupPriceCustomerGroupInProduct - */ - public function testGroupPriceCustomerGroupInProduct($customerGroup) - { - $groupPrice = 80; - $convertedPrice = 56.23; - $this->productMock->expects($this->exactly(2)) - ->method('getCustomerGroupId') - ->will($this->returnValue($customerGroup)); - $this->productMock->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($this->productResourceMock)); - $this->productResourceMock->expects($this->once()) - ->method('getAttribute') - ->with($this->equalTo('group_price')) - ->will($this->returnValue($this->attributeMock)); - $this->attributeMock->expects($this->once()) - ->method('getBackend') - ->will($this->returnValue($this->backendMock)); - $this->backendMock->expects($this->once()) - ->method('afterLoad') - ->with($this->equalTo($this->productMock)) - ->will($this->returnValue($this->backendMock)); - $this->productMock->expects($this->once()) - ->method('getData') - ->with( - $this->equalTo('group_price'), - $this->equalTo(null) - ) - ->will($this->returnValue( - [ - [ - 'cust_group' => $customerGroup, - 'website_price' => $groupPrice, - ], - ] - - )); - $this->priceCurrencyMock->expects($this->once()) - ->method('convertAndRound') - ->with($groupPrice) - ->will($this->returnValue($convertedPrice)); - $this->assertEquals($convertedPrice, $this->groupPrice->getValue()); - } - - public function dataProviderGroupPriceCustomerGroupInProduct() - { - return [ - [0], - [3], - ]; - } - - /** - * test get group price, attribute is not set - */ - public function testGroupPriceAttributeIsNotSet() - { - $this->productMock->expects($this->exactly(2)) - ->method('getCustomerGroupId') - ->will($this->returnValue(3)); - $this->productMock->expects($this->once()) - ->method('getResource') - ->will($this->returnValue($this->productResourceMock)); - $this->productResourceMock->expects($this->once()) - ->method('getAttribute') - ->with($this->equalTo('group_price')) - ->will($this->returnValue(null)); - $this->assertFalse($this->groupPrice->getValue()); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Setup/CategorySetupTest.php b/app/code/Magento/Catalog/Test/Unit/Setup/CategorySetupTest.php index 4d6d8b722021bc3779c795eb6d16c47c28428867..6130294814ad8ef68c0f3892ee29478de3525dc5 100644 --- a/app/code/Magento/Catalog/Test/Unit/Setup/CategorySetupTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Setup/CategorySetupTest.php @@ -76,7 +76,6 @@ class CategorySetupTest extends \PHPUnit_Framework_TestCase 'thumbnail', 'media_gallery', 'old_id', - 'group_price', 'tier_price', 'color', 'news_from_date', diff --git a/app/code/Magento/Catalog/Ui/Component/FilterFactory.php b/app/code/Magento/Catalog/Ui/Component/FilterFactory.php index a32daf19149a530ad1b685b5ac9b200dc8fca28f..0cd46daccf81ceeef50d513d55dff83f2a35b68d 100644 --- a/app/code/Magento/Catalog/Ui/Component/FilterFactory.php +++ b/app/code/Magento/Catalog/Ui/Component/FilterFactory.php @@ -34,15 +34,19 @@ class FilterFactory /** * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context + * @param array $config * @return \Magento\Ui\Component\Listing\Columns\ColumnInterface */ - public function create($attribute, $context) + public function create($attribute, $context, $config = []) { $columnName = $attribute->getAttributeCode(); - $config = [ - 'dataScope' => $columnName, - 'label' => __($attribute->getDefaultFrontendLabel()), - ]; + $config = array_merge( + [ + 'dataScope' => $columnName, + 'label' => __($attribute->getDefaultFrontendLabel()), + ], + $config + ); if ($attribute->usesSource() && $attribute->getSourceModel()) { $config['options'] = $attribute->getSource()->getAllOptions(); $config['caption'] = __('Select...'); diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php new file mode 100644 index 0000000000000000000000000000000000000000..aff12f7aed976812a062ceba2c64be85aab18b6e --- /dev/null +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Ui\Component\Listing\Attribute; + +abstract class AbstractRepository implements RepositoryInterface +{ + /** + * @var null|\Magento\Catalog\Api\Data\ProductAttributeInterface[] + */ + protected $attributes; + + /** @var \Magento\Framework\Api\SearchCriteriaBuilder */ + protected $searchCriteriaBuilder; + + /** + * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository + * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder + */ + public function __construct( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository, + \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder + ) { + $this->productAttributeRepository = $productAttributeRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * {@inheritdoc} + */ + abstract protected function buildSearchCriteria(); + + /** + * @return \Magento\Catalog\Api\Data\ProductAttributeInterface[] + */ + public function getList() + { + if (null == $this->attributes) { + $this->attributes = $this->productAttributeRepository + ->getList($this->buildSearchCriteria()) + ->getItems(); + } + return $this->attributes; + } +} diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/Repository.php b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/Repository.php new file mode 100644 index 0000000000000000000000000000000000000000..c954ec8b636df3f864961d48d3e03cd320680a95 --- /dev/null +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/Repository.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Ui\Component\Listing\Attribute; + +class Repository extends AbstractRepository +{ + /** + * {@inheritdoc} + */ + protected function buildSearchCriteria() + { + return $this->searchCriteriaBuilder->addFilter('additional_table.is_used_in_grid', 1)->create(); + } +} diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/RepositoryInterface.php b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/RepositoryInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b65cce8055c69d079c8638c192a75fabcfc6eb39 --- /dev/null +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/RepositoryInterface.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Ui\Component\Listing\Attribute; + +interface RepositoryInterface +{ + /** + * Get attributes + * + * @return \Magento\Catalog\Api\Data\ProductAttributeInterface[] + */ + public function getList(); +} diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/AttributeRepository.php b/app/code/Magento/Catalog/Ui/Component/Listing/AttributeRepository.php deleted file mode 100644 index 4f7351a3222fb1dc87ce9152c881653b2d17c962..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/Ui/Component/Listing/AttributeRepository.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Ui\Component\Listing; - -class AttributeRepository extends \Magento\Ui\Component\Listing\Columns -{ - /** - * @var null|\Magento\Catalog\Api\Data\ProductAttributeInterface[] - */ - private $attributes; - - /** - * @param \Magento\Catalog\Model\Resource\Product\Attribute\Collection $attributeCollection - * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository - */ - public function __construct( - \Magento\Catalog\Model\Resource\Product\Attribute\Collection $attributeCollection, - \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository - ) { - $this->attributeCollection = $attributeCollection; - $this->productAttributeRepository = $productAttributeRepository; - } - - /** - * @return \Magento\Catalog\Api\Data\ProductAttributeInterface[] - */ - public function getList() - { - if (null == $this->attributes) { - $this->attributes = []; - $this->attributeCollection->addIsUsedInGridFilter(); - foreach ($this->attributeCollection as $attribute) { - $attribute = $this->productAttributeRepository->get($attribute->getAttributeId()); - $this->attributes[] = $attribute; - } - } - return $this->attributes; - } -} diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php index 47b621b0bfceffec2188cbae6264d6b80d32d969..2e0243e5875aaba35f44914ba5eebc908d7d1470 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php @@ -12,17 +12,20 @@ class Columns extends \Magento\Ui\Component\Listing\Columns */ const DEFAULT_COLUMNS_MAX_ORDER = 100; + /** @var \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface */ + protected $attributeRepository; + /** * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context * @param \Magento\Catalog\Ui\Component\ColumnFactory $columnFactory - * @param AttributeRepository $attributeRepository + * @param \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository * @param array $components * @param array $data */ public function __construct( \Magento\Framework\View\Element\UiComponent\ContextInterface $context, \Magento\Catalog\Ui\Component\ColumnFactory $columnFactory, - \Magento\Catalog\Ui\Component\Listing\AttributeRepository $attributeRepository, + \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository, array $components = [], array $data = [] ) { diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Filters.php b/app/code/Magento/Catalog/Ui/Component/Listing/Filters.php index 9c2950a596bca51b0c183efce2708bd9a19ecc27..ba70b679b5e4e4e44781ad1a8363b07e7ce05ad6 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Filters.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Filters.php @@ -8,21 +8,21 @@ namespace Magento\Catalog\Ui\Component\Listing; class Filters extends \Magento\Ui\Component\Filters { /** - * @var AttributeRepository + * @var \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface */ protected $attributeRepository; /** * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context * @param \Magento\Catalog\Ui\Component\FilterFactory $filterFactory - * @param AttributeRepository $attributeRepository + * @param \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository * @param array $components * @param array $data */ public function __construct( \Magento\Framework\View\Element\UiComponent\ContextInterface $context, \Magento\Catalog\Ui\Component\FilterFactory $filterFactory, - \Magento\Catalog\Ui\Component\Listing\AttributeRepository $attributeRepository, + \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository, array $components = [], array $data = [] ) { diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 5cf13196cbd26779ea6c7619a34636e9aef6163b..295f42a5b19495d8a1548cb840c1ba06e3de6a17 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -79,4 +79,5 @@ <type name="Magento\Catalog\Model\Product\Action"> <plugin name="invalidate_pagecache_after_update_product_attributes" type="Magento\Catalog\Plugin\Model\Product\Action\UpdateAttributesFlushCache"/> </type> + <preference for="Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface" type="Magento\Catalog\Ui\Component\Listing\Attribute\Repository"/> </config> diff --git a/app/code/Magento/Catalog/etc/catalog_attributes.xml b/app/code/Magento/Catalog/etc/catalog_attributes.xml index 50470a73308b4cc5eeca046ba0ce6f9f6314e560..3047ea441e2e348415f0d1fc9cfa005eb938c58d 100644 --- a/app/code/Magento/Catalog/etc/catalog_attributes.xml +++ b/app/code/Magento/Catalog/etc/catalog_attributes.xml @@ -47,7 +47,6 @@ <attribute name="image" /> <attribute name="category_ids" /> <attribute name="media_gallery_content" /> - <attribute name="group_price" /> <attribute name="tier_price" /> <attribute name="gift_message_available" /> <attribute name="inventory_manage_stock" /> diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index caa0844fbb8f2aa35ce96eda460e0366bf21de4e..c8e2e9645cde16b668f7f8695af7a099cb76b615 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -62,13 +62,18 @@ <argument name="changelog" xsi:type="object" shared="false">Magento\Framework\Mview\View\ChangelogInterface</argument> </arguments> </type> + <type name="Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool"> + <arguments> + <argument name="mediaGalleryEntryConvertersCollection" xsi:type="array"> + <item name="image" xsi:type="object">Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter</item> + </argument> + </arguments> + </type> <type name="Magento\Catalog\Helper\Product"> <arguments> - <argument name="typeSwitcherLabel" xsi:type="string">Virtual</argument> <argument name="catalogSession" xsi:type="object">Magento\Catalog\Model\Session\Proxy</argument> <argument name="reindexPriceIndexerData" xsi:type="array"> <item name="byDataResult" xsi:type="array"> - <item name="group_price_changed" xsi:type="string">group_price_changed</item> <item name="tier_price_changed" xsi:type="string">tier_price_changed</item> </item> <item name="byDataChange" xsi:type="array"> @@ -303,7 +308,6 @@ <item name="regular_price" xsi:type="string">Magento\Catalog\Pricing\Price\RegularPrice</item> <item name="final_price" xsi:type="string">Magento\Catalog\Pricing\Price\FinalPrice</item> <item name="tier_price" xsi:type="string">Magento\Catalog\Pricing\Price\TierPrice</item> - <item name="group_price" xsi:type="string">Magento\Catalog\Pricing\Price\GroupPrice</item> <item name="special_price" xsi:type="string">Magento\Catalog\Pricing\Price\SpecialPrice</item> <item name="base_price" xsi:type="string">Magento\Catalog\Pricing\Price\BasePrice</item> <item name="custom_option_price" xsi:type="string">Magento\Catalog\Pricing\Price\CustomOptionPrice</item> @@ -456,8 +460,6 @@ <preference for="\Magento\Catalog\Api\CategoryLinkManagementInterface" type="\Magento\Catalog\Model\CategoryLinkManagement" /> <preference for="\Magento\Catalog\Api\CategoryLinkRepositoryInterface" type="\Magento\Catalog\Model\CategoryLinkRepository" /> <preference for="Magento\Catalog\Api\Data\ProductCustomOptionInterface" type="Magento\Catalog\Model\Product\Option" /> - <preference for="Magento\Catalog\Api\ProductGroupPriceManagementInterface" type="Magento\Catalog\Model\Product\GroupPriceManagement" /> - <preference for="Magento\Catalog\Api\Data\ProductGroupPriceInterface" type="Magento\Catalog\Model\Product\GroupPrice" /> <preference for="Magento\Catalog\Api\ProductCustomOptionRepositoryInterface" type="\Magento\Catalog\Model\Product\Option\Repository" /> <preference for="Magento\Catalog\Api\Data\ProductCustomOptionTypeInterface" type="Magento\Catalog\Model\Product\Option\Type" /> <preference for="Magento\Catalog\Api\ProductTierPriceManagementInterface" type="\Magento\Catalog\Model\Product\TierPriceManagement" /> diff --git a/app/code/Magento/Catalog/etc/eav_attributes.xml b/app/code/Magento/Catalog/etc/eav_attributes.xml index 3ba4dfae9d8b082a7be50d6119afb126662d7a50..37623d9894b7f57a3c9ec1d8d4d6246b1ac69a16 100644 --- a/app/code/Magento/Catalog/etc/eav_attributes.xml +++ b/app/code/Magento/Catalog/etc/eav_attributes.xml @@ -26,9 +26,6 @@ <field code="is_filterable" locked="true" /> <field code="is_filterable_in_search" locked="true" /> </attribute> - <attribute code="group_price"> - <field code="is_searchable" locked="true" /> - </attribute> <attribute code="price_type"> <field code="is_searchable" locked="true" /> </attribute> diff --git a/app/code/Magento/Catalog/etc/extension_attributes.xml b/app/code/Magento/Catalog/etc/extension_attributes.xml new file mode 100644 index 0000000000000000000000000000000000000000..53c5e40db57372fe468aa8211427e722c380b70d --- /dev/null +++ b/app/code/Magento/Catalog/etc/extension_attributes.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/extension_attributes.xsd"> + <extension_attributes for="Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface"> + <attribute code="video_content" type="Magento\Framework\Api\Data\VideoContentInterface" /> + </extension_attributes> +</config> \ No newline at end of file diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml index ed2f204b24e5c220997f4a0b4ba0922bfe407d9f..ace5d6f78eb07415e33b681700926508558ba3b1 100644 --- a/app/code/Magento/Catalog/etc/module.xml +++ b/app/code/Magento/Catalog/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> - <module name="Magento_Catalog" setup_version="2.0.0"> + <module name="Magento_Catalog" setup_version="2.0.1"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Cms"/> diff --git a/app/code/Magento/Catalog/etc/mview.xml b/app/code/Magento/Catalog/etc/mview.xml index 2c84b4d8d9fde9169976745775d2cb13734259ea..292dc1d421642ca866937f3aeb18988048ed55a9 100644 --- a/app/code/Magento/Catalog/etc/mview.xml +++ b/app/code/Magento/Catalog/etc/mview.xml @@ -35,9 +35,8 @@ <table name="catalog_product_entity_datetime" entity_column="entity_id" /> <table name="catalog_product_entity_decimal" entity_column="entity_id" /> <table name="catalog_product_entity_gallery" entity_column="entity_id" /> - <table name="catalog_product_entity_group_price" entity_column="entity_id" /> <table name="catalog_product_entity_int" entity_column="entity_id" /> - <table name="catalog_product_entity_media_gallery" entity_column="entity_id" /> + <table name="catalog_product_entity_media_gallery" entity_column="value_id" /> <table name="catalog_product_entity_media_gallery_value" entity_column="entity_id" /> <table name="catalog_product_entity_text" entity_column="entity_id" /> <table name="catalog_product_entity_tier_price" entity_column="entity_id" /> @@ -49,7 +48,6 @@ <table name="catalog_product_entity" entity_column="entity_id" /> <table name="catalog_product_entity_datetime" entity_column="entity_id" /> <table name="catalog_product_entity_decimal" entity_column="entity_id" /> - <table name="catalog_product_entity_group_price" entity_column="entity_id" /> <table name="catalog_product_entity_int" entity_column="entity_id" /> <table name="catalog_product_entity_tier_price" entity_column="entity_id" /> </subscriptions> diff --git a/app/code/Magento/Catalog/etc/webapi.xml b/app/code/Magento/Catalog/etc/webapi.xml index ec8458cd8c9a7ebeda59b7dfdbde2fbb77b05bf6..498863eeb9ab235122afc8a9517acb198da17435 100644 --- a/app/code/Magento/Catalog/etc/webapi.xml +++ b/app/code/Magento/Catalog/etc/webapi.xml @@ -196,7 +196,7 @@ <resource ref="anonymous"/> </resources> </route> - <route url="/V1/products/:sku/media/:imageId" method="GET"> + <route url="/V1/products/:sku/media/:entryId" method="GET"> <service class="Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface" method="get"/> <resources> <resource ref="anonymous"/> @@ -227,26 +227,6 @@ </resources> </route> - <!-- Group Price --> - <route url="/V1/products/:sku/group-prices/" method="GET"> - <service class="Magento\Catalog\Api\ProductGroupPriceManagementInterface" method="getList"/> - <resources> - <resource ref="anonymous"/> - </resources> - </route> - <route url="/V1/products/:sku/group-prices/:customerGroupId/price/:price" method="POST"> - <service class="Magento\Catalog\Api\ProductGroupPriceManagementInterface" method="add"/> - <resources> - <resource ref="Magento_Catalog::catalog"/> - </resources> - </route> - <route url="/V1/products/:sku/group-prices/:customerGroupId/" method="DELETE"> - <service class="Magento\Catalog\Api\ProductGroupPriceManagementInterface" method="remove"/> - <resources> - <resource ref="Magento_Catalog::catalog"/> - </resources> - </route> - <!-- Tier Price --> <route url="/V1/products/:sku/group-prices/:customerGroupId/tiers" method="GET"> <service class="Magento\Catalog\Api\ProductTierPriceManagementInterface" method="getList"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml index 55e9ebee91f5bcbbcc6b77c4d81fd3638dec7592..c0605b000485fe572e5e5fc365e531e1f63bcb3d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_new.xml @@ -28,6 +28,7 @@ <arguments> <argument name="panels_class" xsi:type="string">admin__scope-old</argument> </arguments> + <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\ChildTab" name="product_tabs.child-tab" as="child-tab" template="Magento_Catalog::product/edit/tabs/child_tab.phtml"/> <block class="Magento\Backend\Block\Widget\Tab" name="product_tabs.customer_options" as="customer_options"> <arguments> <argument name="label" xsi:type="string" translate="true">Custom Options</argument> @@ -37,6 +38,12 @@ <argument name="class" xsi:type="string">ajax</argument> </arguments> </block> + <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Inventory" name="product_tabs.advanced-inventory" as="advanced-inventory"> + <arguments> + <argument name="label" xsi:type="string" translate="true">Advanced Inventory</argument> + <argument name="group_code" xsi:type="string">advanced</argument> + </arguments> + </block> <block class="Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Alerts" name="product_tabs.product-alerts" as="product-alerts"> <arguments> <argument name="label" xsi:type="string" translate="true">Product Alerts</argument> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml index 46c250b71ce6adfa4b71ff6262a6f7e062bfb961..95561d18cba3848063b9e310c74966323c52aa7d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml @@ -8,7 +8,7 @@ /** @var $block \Magento\Eav\Block\Adminhtml\Attribute\Edit\Options\Options */ ?> -<fieldset class="fieldset"> +<fieldset class="fieldset ignore-validate"> <legend class="legend"><span><?php /* @escapeNotVerified */ echo __('Manage Options (values of your attribute)') ?></span></legend> <div id="manage-options-panel"> <table class="admin__control-table"> @@ -67,110 +67,21 @@ </td> </tr> </script> - <script> -require([ - 'jquery', - 'mage/template', - 'jquery/ui', - 'prototype' -], function(jQuery, mageTemplate){ - - var optionDefaultInputType = 'radio'; - - var attributeOption = { - table: $('attribute-options-table'), - itemCount: 0, - totalItems: 0, - isReadOnly: <?php echo (int)$block->getReadOnly(); ?>, - add: function(data) { - this.template = mageTemplate('#row-template'); - var isNewOption = false; - if (typeof data.id == 'undefined') { - data = { - 'id': 'option_' + this.itemCount, - 'sort_order': this.itemCount + 1 - }; - isNewOption = true; - } - if (!data.intype) { - data.intype = optionDefaultInputType; - } - - Element.insert($$('[data-role=options-container]')[0], this.template({data: data})); - if (isNewOption && !this.isReadOnly) { - this.enableNewOptionDeleteButton(data.id); - } - this.itemCount++; - this.totalItems++; - this.updateItemsCountField(); - }, - remove: function(event) { - var element = $(Event.findElement(event, 'tr')); // !!! Button already have table parent in safari - // Safari workaround - element.ancestors().each(function(parentItem) { - if (parentItem.hasClassName('option-row')) { - element = parentItem; - throw $break; - } else if (parentItem.hasClassName('box')) { - throw $break; - } - }); - - if (element) { - var elementFlags = element.getElementsByClassName('delete-flag'); - if (elementFlags[0]) { - elementFlags[0].value = 1; - } - - element.addClassName('no-display'); - element.addClassName('template'); - element.hide(); - this.totalItems--; - this.updateItemsCountField(); - } - }, - updateItemsCountField: function() { - $('option-count-check').value = this.totalItems > 0 ? '1' : ''; - }, - enableNewOptionDeleteButton: function(id) { - $$('#delete_button_container_' + id + ' button').each(function(button) { - button.enable(); - button.removeClassName('disabled'); - }); - }, - }; - - if ($('add_new_option_button')) { - Event.observe('add_new_option_button', 'click', attributeOption.add.bind(attributeOption)); + <?php + $values = []; + foreach($block->getOptionValues() as $value) { + array_push($values, $value->getData()); } - - $('manage-options-panel').on('click', '.delete-option', function(event, element) { - attributeOption.remove(event); - }); - - <?php foreach ($block->getOptionValues() as $_value): ?> - attributeOption.add(<?php /* @escapeNotVerified */ echo $_value->toJson() ?>); - <?php endforeach; ?> - <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?> - jQuery(function($) { - $('[data-role=options-container]').sortable({ - distance: 8, - tolerance: "pointer", - cancel: 'input, button', - axis: 'y', - update: function() { - $('[data-role=options-container] [data-role=order]').each(function(index, element) { - $(element).val(index + 1); - }); + ?> + <script type="text/x-magento-init"> + { + "*": { + "Magento_Catalog/js/options": { + "attributesData": <?php /* @escapeNotVerified */ echo json_encode($values); ?>, + "isSortable": <?php echo (int)(!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) ?>, + "isReadOnly": <?php echo (int)$block->getReadOnly(); ?> + } } - }); - }); - <?php endif; ?> - - window.attributeOption = attributeOption; - window.optionDefaultInputType = optionDefaultInputType; - //]]> - -}); -</script> + } + </script> </fieldset> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml index bfafbcb02d2209513e6985cad5f610a68819867c..3bce181616dab3a8a99102a7105ba6dea9bd880e 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit.phtml @@ -61,12 +61,12 @@ <script> require([ "jquery", + "Magento_Catalog/catalog/type-switcher", "underscore", "mage/mage", - "Magento_Catalog/catalog/type-switcher", "mage/backend/tabs", "domReady!" -], function($){ +], function($, TypeSwitcher){ var $form = $('[data-form=edit-product]'); $form.data('typeSwitcher', new TypeSwitcher(<?php /* @escapeNotVerified */ echo $block->getTypeSwitcherData();?>).bindAll()); @@ -233,7 +233,7 @@ require([ $form.on('changeAttributeSet', function(event, data) { if (data.label) { $('#product-template-suggest-container .action-toggle>span').text(data.label); - $('[data-id=affected-attribute-set-selector] [data-id=current-attribute-set] [data-role=name-container]').text(data.label); + $('[data-role=affected-attribute-set-selector] [data-role=name-container]').text(data.label); } $('#product-template-suggest').suggest('option', "currentlySelected", data.id).val(''); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml index aa49671e94509083573c814575a38a9e9af69140..67b1e95e3084663f375efad98714f295ac897c41 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options.phtml @@ -15,7 +15,7 @@ <span><?php /* @escapeNotVerified */ echo __('Custom Options') ?></span> </strong> </div> - <div class="fieldset-wrapper-content" id="product-custom-options-content"> + <div class="fieldset-wrapper-content" id="product-custom-options-content" data-role="product-custom-options-content"> <fieldset class="fieldset"> <div class="messages"> <div class="message message-error" id="dynamic-price-warning" style="display: none;"> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml index dc8c8f8ce1990920d70ddf8c4d2f10753863b3e3..d8d09c8ff7758705783c1486110a99c0a50abdde 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml @@ -31,7 +31,9 @@ disabled="disabled" <?php endif; ?>> </td> - <td class="opt-price-type"><?php echo $block->getPriceTypeSelectHtml() ?><%- data.checkboxScopePrice %></td> + <td class="opt-price-type"> + <?php echo $block->getPriceTypeSelectHtml('data-attr="price-type"') ?><%- data.checkboxScopePrice %> + </td> <?php else : ?> <input id="product_option_<%- data.option_id %>_price" name="product[options][<%- data.option_id %>][price]" type="hidden"> <input id="product_option_<%- data.option_id %>_price_type" name="product[options][<%- data.option_id %>][price_type]" type="hidden"> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml index afe552f4ab82954c90fd8e00df8982f2661858e8..7be294debacf1406de7cb4ecb41db66dd0e386e5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/file.phtml @@ -31,7 +31,7 @@ disabled="disabled" <?php endif; ?>> </td> - <td class="opt-price-type"><?php echo $block->getPriceTypeSelectHtml() ?><%- data.checkboxScopePrice %></td> + <td class="opt-price-type"><?php echo $block->getPriceTypeSelectHtml('data-attr="price-type"') ?><%- data.checkboxScopePrice %></td> <?php else : ?> <input name="product[options][<%- data.option_id %>][price]" type="hidden"> <input id="product_option_<%- data.option_id %>_price_type" name="product[options][<%- data.option_id %>][price_type]" type="hidden"> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml index eae5c113215ce90382fc023da67943e5a64290de..da7b6eef24963254ac343ef300037157b25ba00b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/select.phtml @@ -57,7 +57,9 @@ disabled="disabled" <?php endif; ?>> </td> - <td class="col-price-type select-opt-price-type"><?php /* @escapeNotVerified */ echo $block->getPriceTypeSelectHtml('<% if (typeof data.scopePriceDisabled != "undefined" && data.scopePriceDisabled != null) { %> disabled="disabled" <% } %>') ?><%- data.checkboxScopePrice %></td> + <td class="col-price-type select-opt-price-type"> + <?php /* @escapeNotVerified */ echo $block->getPriceTypeSelectHtml('data-attr="price-type" <% if (typeof data.scopePriceDisabled != "undefined" && data.scopePriceDisabled != null) { %> disabled="disabled" <% } %>') ?><%- data.checkboxScopePrice %> + </td> <?php else : ?> <input id="product_option_<%- data.id %>_select_<%- data.select_id %>_price" name="product[options][<%- data.id %>][values][<%- data.select_id %>][price]" type="hidden"> <input id="product_option_<%- data.id %>_select_<%- data.select_id %>_price_type" name="product[options][<%- data.id %>][values][<%- data.select_id %>][price_type]" type="hidden"> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml index bcc6f5171dc64be5563e181f1f230f6ff58b6fe1..769c6c1749ed3fbb2611441567d717b1852c8f6f 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/text.phtml @@ -32,7 +32,7 @@ disabled="disabled" <?php endif; ?>> </td> - <td class="opt-price-type"><?php echo $block->getPriceTypeSelectHtml() ?><%- data.checkboxScopePrice %></td> + <td class="opt-price-type"><?php echo $block->getPriceTypeSelectHtml('data-attr="price-type"') ?><%- data.checkboxScopePrice %></td> <?php else : ?> <input id="product_option_<%- data.option_id %>_price" name="product[options][<%- data.option_id %>][price]" type="hidden"> <input id="product_option_<%- data.option_id %>_price_type" name="product[options][<%- data.option_id %>][price_type]" type="hidden"> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/group.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/group.phtml deleted file mode 100644 index d9f179b7ab50d5eafff5f1f685d16fd9ee7a79d5..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/price/group.phtml +++ /dev/null @@ -1,163 +0,0 @@ - <?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -?> -<?php -/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price\Group */ -$element = $block->getElement(); -$_htmlId = $block->getElement()->getHtmlId(); -$_htmlClass = $block->getElement()->getClass(); -$_htmlName = $block->getElement()->getName(); -$_readonly = $block->getElement()->getReadonly(); -$_priceValueValidation = $block->getPriceValidation('validate-zero-or-greater'); - -$_showWebsite = $block->isMultiWebsites(); -?> -<div class="field" id="attribute-<?php /* @escapeNotVerified */ echo $_htmlId?>-container" data-attribute-code="<?php /* @escapeNotVerified */ echo $_htmlId?>" - data-apply-to="<?php echo $block->escapeHtml( - $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode( - $element->hasEntityAttribute() ? $element->getEntityAttribute()->getApplyTo() : [] - ) - )?>"> - <label class="label"><span><?php /* @escapeNotVerified */ echo $block->getElement()->getLabel(); ?></span></label> - <div class="control"> - <table class="admin__control-table" id="group_prices_table"> - <thead> - <tr> - <th <?php if (!$_showWebsite): ?>style="display: none;"<?php endif; ?>><?php /* @escapeNotVerified */ echo __('Web Site'); ?></th> - <th class="required"><?php /* @escapeNotVerified */ echo __('Customer Group'); ?></th> - <th class="required"><?php /* @escapeNotVerified */ echo $block->getPriceColumnHeader(__('Price')); ?></th> - <th class="col-delete"><?php /* @escapeNotVerified */ echo __('Action'); ?></th> - </tr> - </thead> - <tbody id="<?php /* @escapeNotVerified */ echo $_htmlId; ?>_container"></tbody> - <tfoot> - <tr> - <td colspan="<?php if (!$_showWebsite): ?>5<?php else: ?>4<?php endif; ?>" class="col-actions-add"><?php echo $block->getAddButtonHtml(); ?></td> - </tr> - </tfoot> - </table> - -<script> -require([ - 'mage/template', - "prototype", - "mage/adminhtml/form" -], function (mageTemplate) { - -//<![CDATA[ -var groupPriceRowTemplate = '<tr>' - + '<td<?php if (!$_showWebsite): ?> style="display:none"<?php endif; ?>>' - + '<select class="<?php /* @escapeNotVerified */ echo $_htmlClass; ?> required-entry" name="<?php /* @escapeNotVerified */ echo $_htmlName; ?>[<%- data.index %>][website_id]" id="group_price_row_<%- data.index %>_website">' - <?php foreach ($block->getWebsites() as $_websiteId => $_info) : ?> - + '<option value="<?php /* @escapeNotVerified */ echo $_websiteId; ?>"><?php /* @escapeNotVerified */ echo $block->escapeJsQuote($block->escapeHtml($_info['name'])); ?><?php if (!empty($_info['currency'])) : ?> [<?php echo $block->escapeHtml($_info['currency']); ?>]<?php endif; ?></option>' - <?php endforeach; ?> - + '</select></td>' - + '<td><select class="<?php /* @escapeNotVerified */ echo $_htmlClass; ?> custgroup required-entry" name="<?php /* @escapeNotVerified */ echo $_htmlName; ?>[<%- data.index %>][cust_group]" id="group_price_row_<%- data.index %>_cust_group">' - <?php foreach ($block->getCustomerGroups() as $_groupId => $_groupName): ?> - + '<option value="<?php /* @escapeNotVerified */ echo $_groupId; ?>"><?php /* @escapeNotVerified */ echo $block->escapeJsQuote($block->escapeHtml($_groupName)); ?></option>' - <?php endforeach; ?> - + '</select></td>' - + '<td><input class="<?php /* @escapeNotVerified */ echo $_htmlClass; ?> required-entry <?php /* @escapeNotVerified */ echo $_priceValueValidation; ?>" type="text" name="<?php /* @escapeNotVerified */ echo $_htmlName; ?>[<%- data.index %>][price]" value="<%- data.price %>" id="group_price_row_<%- data.index %>_price" /></td>' - + '<td class="col-delete"><input type="hidden" name="<?php /* @escapeNotVerified */ echo $_htmlName; ?>[<%- data.index %>][delete]" class="delete" value="" id="group_price_row_<%- data.index %>_delete" />' - + '<button title="<?php /* @escapeNotVerified */ echo __('Delete Group Price'); ?>" type="button" class="action- scalable delete icon-btn delete-product-option" id="group_price_row_<%- data.index %>_delete_button" onclick="return groupPriceControl.deleteItem(event);">' - + '<span><?php /* @escapeNotVerified */ echo __('Delete'); ?></span></button></td>' - + '</tr>'; - -var groupPriceControl = { - template: mageTemplate(groupPriceRowTemplate), - itemsCount: 0, - addItem : function () { - <?php if ($_readonly): ?> - if (arguments.length < 3) { - return; - } - <?php endif; ?> - var data = { - website_id: '<?php /* @escapeNotVerified */ echo $block->getDefaultWebsite(); ?>', - group: '<?php /* @escapeNotVerified */ echo $block->getDefaultCustomerGroup(); ?>', - price: '', - readOnly: false, - index: this.itemsCount++ - }; - - if(arguments.length >= 3) { - data.website_id = arguments[0]; - data.group = arguments[1]; - data.price = arguments[2]; - } - if (arguments.length == 4) { - data.readOnly = arguments[3]; - } - - Element.insert($('<?php /* @escapeNotVerified */ echo $_htmlId; ?>_container'), { - bottom : this.template({ - data: data - }) - }); - - $('group_price_row_' + data.index + '_cust_group').value = data.group; - $('group_price_row_' + data.index + '_website').value = data.website_id; - - <?php if ($block->isShowWebsiteColumn() && !$block->isAllowChangeWebsite()): ?> - var websiteElement = $('group_price_row_' + data.index + '_website'); - var websiteCaption = websiteElement.options[websiteElement.selectedIndex].text; - - websiteElement.insert({after:'<span class="website-name">' + websiteCaption + '</span>'}); - websiteElement.hide(); - <?php endif;?> - - if (data.readOnly == '1') { - ['website', 'cust_group', 'price', 'delete'].each(function(element_suffix) { - $('group_price_row_' + data.index + '_' + element_suffix).disabled = true; - }); - $('group_price_row_' + data.index + '_delete_button').hide(); - } - - <?php if ($_readonly): ?> - $('<?php /* @escapeNotVerified */ echo $_htmlId; ?>_container').select('input', 'select').each(this.disableElement); - $('<?php /* @escapeNotVerified */ echo $_htmlId; ?>_container').up('table').select('button').each(this.disableElement); - <?php else: ?> - $('<?php /* @escapeNotVerified */ echo $_htmlId; ?>_container').select('input', 'select').each(function(element) { - Event.observe(element, 'change', element.setHasChanges.bind(element)); - }); - <?php endif; ?> - }, - disableElement: function(element) { - element.disabled = true; - element.addClassName('disabled'); - }, - deleteItem: function(event) { - var tr = Event.findElement(event, 'tr'); - if (tr) { - Element.select(tr, '.delete').each(function(element) { - element.value='1'; - }); - Element.select(tr, ['input', 'select']).each(function(element) { - element.hide(); - }); - Element.hide(tr); - Element.addClassName(tr, 'no-display template'); - } - return false; - } -}; -<?php foreach ($block->getValues() as $_item) : ?> -groupPriceControl.addItem('<?php /* @escapeNotVerified */ echo $_item['website_id']; ?>', '<?php /* @escapeNotVerified */ echo $_item['cust_group']; ?>', '<?php /* @escapeNotVerified */ echo $_item['price']; ?>', <?php echo (int)!empty($_item['readonly']); ?>); -<?php endforeach; ?> -<?php if ($_readonly) : ?> -$('<?php /* @escapeNotVerified */ echo $_htmlId; ?>_container').up('table').select('button') - .each(groupPriceControl.disableElement); -<?php endif; ?> -//]]> - -window.groupPriceControl = groupPriceControl; -}); -</script> - </div> -</div> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml index 3f6215626ad827c7a2791828b4886b958ce4bfc4..d3c382f9276b8a2d9df9ec1473fbe1707779fc42 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/attribute/search.phtml @@ -63,7 +63,7 @@ data: {attribute_id: ui.item.id, template_id: templateId, group: $(this).data('group')}, showLoader: true }).done(function() { - setTimeout(function() { //do defered + setTimeout(function() { //do deferred $('[data-form=edit-product]').trigger('changeAttributeSet', {id: templateId}); }, 10); }); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/base_image.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/base_image.phtml new file mode 100644 index 0000000000000000000000000000000000000000..9f68f3d2810452ed4cfe477b51d37c65594a43f8 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/base_image.phtml @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<div id="<?php /* @escapeNotVerified */ echo $htmlId;?>-container" class="images" + data-mage-init='{"baseImage":{}}' + data-max-file-size="<?php /* @escapeNotVerified */ echo $fileMaxSize;?>" + > + <div class="image image-placeholder"> + <input type="file" + name="image" + data-url="<?php /* @escapeNotVerified */ echo $uploadUrl;?>" + multiple="multiple" /> + <img class="spacer" src="<?php /* @escapeNotVerified */ echo $spacerImage;?>"/> + <p class="image-placeholder-text"><?php /* @escapeNotVerified */ echo $imagePlaceholderText;?></p> + </div> + <script + id="<?php /* @escapeNotVerified */ echo $htmlId;?>-template" + data-template="image" + type="text/x-magento-template"> + <div class="image"> + <img class="spacer" src="<?php /* @escapeNotVerified */ echo $spacerImage;?>"/> + <img + class="product-image" + src="<%- data.url %>" + data-position="<%- data.position %>" + alt="<%- data.label %>" /> + <div class="actions"> + <button + type="button" + class="action-delete" + data-role="delete-button" + title="<?php /* @escapeNotVerified */ echo $deleteImageText;?>"> + <span><?php /* @escapeNotVerified */ echo $deleteImageText;?></span> + </button> + <button + type="button" + class="action-make-base" + data-role="make-base-button" + title="<?php /* @escapeNotVerified */ echo $makeBaseText;?>"> + <span><?php /* @escapeNotVerified */ echo $makeBaseText;?></span> + </button> + <div class="draggable-handle"></div> + </div> + <div class="image-label"></div> + <div class="image-fade"><span><?php /* @escapeNotVerified */ echo $hiddenText;?></span></div> + </div> + </script> +</div> +<span class="action-manage-images" data-activate-tab="image-management"> + <span><?php /* @escapeNotVerified */ echo $imageManagementText;?></span> +</span> +<script> + require([ + 'jquery' + ],function($){ + 'use strict'; + $('[data-activate-tab=image-management]') + .on('click.toggleImageManagementTab', function() { + $('#product_info_tabs_image-management').trigger('click'); + }); + }); +</script> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml index 476506df35de6a6aaea1794b1914d3f300c9ae81..4285cba52e575cd77afacc5dd294c9fd85b8a631 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs.phtml @@ -9,109 +9,101 @@ /** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs */ ?> <?php if (!empty($tabs)): ?> -<?php $tabGroups = [ - \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::BASIC_TAB_GROUP_CODE, - \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::ADVANCED_TAB_GROUP_CODE, -];?> + <?php $tabGroups = [ + \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::BASIC_TAB_GROUP_CODE, + \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::ADVANCED_TAB_GROUP_CODE, + ];?> -<div id="<?php /* @escapeNotVerified */ echo $block->getId() ?>" - data-mage-init='{"tabs":{ - "active": "<?php /* @escapeNotVerified */ echo $block->getActiveTabId() ?>", - "destination": "#<?php /* @escapeNotVerified */ echo $block->getDestElementId() ?>", - "shadowTabs": "<?php /* @escapeNotVerified */ echo $block->getAllShadowTabs()?>", - "tabsBlockPrefix": "<?php /* @escapeNotVerified */ echo $block->getId() ?>_", - "tabIdArgument": "active_tab", - "tabPanelClass": "<?php /* @escapeNotVerified */ echo $block->getPanelsClass() ?>", - "excludedPanel": "<?php /* @escapeNotVerified */ echo $block->getExcludedPanel() ?>", - "groups": "ul.tabs" -}}'> - <?php foreach ($tabGroups as $tabGroupCode): ?> - <?php - $tabGroupId = $block->getId() . '-' . $tabGroupCode; - $isBasic = $tabGroupCode == \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::BASIC_TAB_GROUP_CODE; - $activeCollapsible = $block->isAdvancedTabGroupActive() ? true : false; - ?> + <div id="<?php /* @escapeNotVerified */ echo $block->getId() ?>" + data-mage-init='{"tabs":{ + "active": "<?php /* @escapeNotVerified */ echo $block->getActiveTabId() ?>", + "destination": "#<?php /* @escapeNotVerified */ echo $block->getDestElementId() ?>", + "shadowTabs": "<?php /* @escapeNotVerified */ echo $block->getAllShadowTabs()?>", + "tabsBlockPrefix": "<?php /* @escapeNotVerified */ echo $block->getId() ?>_", + "tabIdArgument": "active_tab", + "tabPanelClass": "<?php /* @escapeNotVerified */ echo $block->getPanelsClass() ?>", + "excludedPanel": "<?php /* @escapeNotVerified */ echo $block->getExcludedPanel() ?>", + "groups": "ul.tabs" + }}'> + <?php foreach ($tabGroups as $tabGroupCode): ?> + <?php + $tabGroupId = $block->getId() . '-' . $tabGroupCode; + $isBasic = $tabGroupCode == \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::BASIC_TAB_GROUP_CODE; + $activeCollapsible = $block->isAdvancedTabGroupActive() ? true : false; + ?> - <div class="admin__page-nav <?php if (!$isBasic): ?> <?php echo '_collapsed';?> <?php endif;?>" - data-role="container" - id="<?php /* @escapeNotVerified */ echo $tabGroupId ?>" - <?php if (!$isBasic): ?> - data-mage-init='{"collapsible":{ - "active": "<?php /* @escapeNotVerified */ echo $activeCollapsible; ?>", - "openedState": "_show", - "closedState": "_hide", - "animate": 200, - "collapsible": true - }}' - <?php endif;?>> + <div class="admin__page-nav <?php if (!$isBasic): ?> <?php echo '_collapsed';?> <?php endif;?>" + data-role="container" + id="<?php /* @escapeNotVerified */ echo $tabGroupId ?>" + <?php if (!$isBasic): ?> + data-mage-init='{"collapsible":{ + "active": "<?php /* @escapeNotVerified */ echo $activeCollapsible; ?>", + "openedState": "_show", + "closedState": "_hide", + "animate": 200, + "collapsible": true + }}' + <?php endif;?>> - <div class="admin__page-nav-title-wrap" <?php /* @escapeNotVerified */ echo $block->getUiId('title') ?> data-role="title"> - <div class="admin__page-nav-title <?php if (!$isBasic): ?> <?php echo '_collapsible';?><?php endif;?>" - data-role="trigger"> - <strong> - <?php /* @escapeNotVerified */ echo $isBasic ? __('Basic Settings') : __('Advanced Settings') ?> - </strong> - <span data-role="title-messages" class="admin__page-nav-title-messages"></span> + <div class="admin__page-nav-title-wrap" <?php /* @escapeNotVerified */ echo $block->getUiId('title') ?> data-role="title"> + <div class="admin__page-nav-title <?php if (!$isBasic): ?> <?php echo '_collapsible';?><?php endif;?>" + data-role="trigger"> + <strong> + <?php /* @escapeNotVerified */ echo $isBasic ? __('Basic Settings') : __('Advanced Settings') ?> + </strong> + <span data-role="title-messages" class="admin__page-nav-title-messages"></span> + </div> </div> - </div> - - <ul <?php /* @escapeNotVerified */ echo $block->getUiId('tab', $tabGroupId) ?> class="tabs admin__page-nav-items" data-role="content"> - <?php foreach ($tabs as $_tab): ?> - <?php if (!$block->canShowTab($_tab) || $_tab->getParentTab() - || ($_tab->getGroupCode() && $_tab->getGroupCode() != $tabGroupCode) - || (!$_tab->getGroupCode() && $isBasic)): continue; endif;?> - <?php $_tabClass = 'tab-item-link ' . $block->getTabClass($_tab) . ' ' . (preg_match('/\s?ajax\s?/', $_tab->getClass()) ? 'notloaded' : '') ?> - <?php $_tabType = (!preg_match('/\s?ajax\s?/', $_tabClass) && $block->getTabUrl($_tab) != '#') ? 'link' : '' ?> - <?php $_tabHref = $block->getTabUrl($_tab) == '#' ? '#' . $block->getTabId($_tab) . '_content' : $block->getTabUrl($_tab) ?> - <li class="admin__page-nav-item <?php if ($block->getTabIsHidden($_tab)): ?> <?php echo "no-display"; ?> <?php endif; ?> " <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'item', $_tab->getId()) ?>> - - <a href="<?php /* @escapeNotVerified */ echo $_tabHref ?>" id="<?php /* @escapeNotVerified */ echo $block->getTabId($_tab) ?>" - name="<?php /* @escapeNotVerified */ echo $block->getTabId($_tab, false) ?>" - title="<?php /* @escapeNotVerified */ echo $block->getTabTitle($_tab) ?>" - class="admin__page-nav-link <?php /* @escapeNotVerified */ echo $_tabClass;?>" - data-tab-type="<?php /* @escapeNotVerified */ echo $_tabType;?>" <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'link', $_tab->getId()) ?>> - - <span><?php echo $block->escapeHtml($block->getTabLabel($_tab)); ?></span> - <span class="admin__page-nav-item-messages" data-role="item-messages"> - <span class="admin__page-nav-item-message _changed"> - <span class="admin__page-nav-item-message-icon"></span> - <span class="admin__page-nav-item-message-tooltip"> - <?php /* @escapeNotVerified */ echo __('Changes have been made to this section that have not been saved.'); ?> + <ul <?php /* @escapeNotVerified */ echo $block->getUiId('tab', $tabGroupId) ?> class="tabs admin__page-nav-items" data-role="content"> + <?php foreach ($tabs as $_tab): ?> + <?php /** @var $_tab \Magento\Backend\Block\Widget\Tab\TabInterface */ ?> + <?php if (!$block->canShowTab($_tab) || $_tab->getParentTab() + || ($_tab->getGroupCode() && $_tab->getGroupCode() != $tabGroupCode) + || (!$_tab->getGroupCode() && $isBasic)): continue; endif;?> + <?php $_tabClass = 'tab-item-link ' . $block->getTabClass($_tab) . ' ' . (preg_match('/\s?ajax\s?/', $_tab->getClass()) ? 'notloaded' : '') ?> + <?php $_tabType = (!preg_match('/\s?ajax\s?/', $_tabClass) && $block->getTabUrl($_tab) != '#') ? 'link' : '' ?> + <?php $_tabHref = $block->getTabUrl($_tab) == '#' ? '#' . $block->getTabId($_tab) . '_content' : $block->getTabUrl($_tab) ?> + <li class="admin__page-nav-item <?php if ($block->getTabIsHidden($_tab)): ?> <?php echo "no-display"; ?> <?php endif; ?> " <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'item', $_tab->getId()) ?>> + <a href="<?php /* @escapeNotVerified */ echo $_tabHref ?>" id="<?php /* @escapeNotVerified */ echo $block->getTabId($_tab) ?>" + name="<?php /* @escapeNotVerified */ echo $block->getTabId($_tab, false) ?>" + title="<?php /* @escapeNotVerified */ echo $block->getTabTitle($_tab) ?>" + class="admin__page-nav-link <?php /* @escapeNotVerified */ echo $_tabClass;?>" + data-tab-type="<?php /* @escapeNotVerified */ echo $_tabType;?>" <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'link', $_tab->getId()) ?> + > + <span><?php echo $block->escapeHtml($block->getTabLabel($_tab)); ?></span> + <span class="admin__page-nav-item-messages" data-role="item-messages"> + <span class="admin__page-nav-item-message _changed"> + <span class="admin__page-nav-item-message-icon"></span> + <span class="admin__page-nav-item-message-tooltip"> + <?php /* @escapeNotVerified */ echo __('Changes have been made to this section that have not been saved.'); ?> + </span> </span> - </span> - <span class="admin__page-nav-item-message _error"> - <span class="admin__page-nav-item-message-icon"></span> - <span class="admin__page-nav-item-message-tooltip"> - <?php /* @escapeNotVerified */ echo __('This tab contains invalid data. Please resolve this before saving.'); ?> + <span class="admin__page-nav-item-message _error"> + <span class="admin__page-nav-item-message-icon"></span> + <span class="admin__page-nav-item-message-tooltip"> + <?php /* @escapeNotVerified */ echo __('This tab contains invalid data. Please resolve this before saving.'); ?> + </span> </span> - </span> - <span class="admin__page-nav-item-message-loader"> - <span class="spinner"> - <span></span><span></span><span></span><span></span> - <span></span><span></span><span></span><span></span> + <span class="admin__page-nav-item-message-loader"> + <span class="spinner"> + <span></span><span></span><span></span><span></span> + <span></span><span></span><span></span><span></span> + </span> </span> - </span> - </span> - </a> - - <div id="<?php /* @escapeNotVerified */ echo $block->getTabId($_tab) ?>_content" class="no-display" - data-tab-panel="<?= /* @escapeNotVerified */ $_tab->getTabId() ?>" - <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'content', $_tab->getId()) ?>> - <?php /* @escapeNotVerified */ echo $block->getTabContent($_tab); ?> - <?php foreach ($tabs as $childTab): ?> - <?php if ($childTab->getParentTab() === $_tab->getId()):?> - <div id="<?php /* @escapeNotVerified */ echo $block->getTabId($childTab) ?>_content" - <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'content', $childTab->getId()) ?>> - <?php /* @escapeNotVerified */ echo $block->getTabContent($childTab); ?> - <?php endif;?> - <?php endforeach; ?> + </span> + </a> + <div id="<?php /* @escapeNotVerified */ echo $block->getTabId($_tab) ?>_content" class="no-display" + data-tab-panel="<?= /* @escapeNotVerified */ $_tab->getTabId() ?>" + <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'content', $_tab->getId()) ?> + > + <?php /* @escapeNotVerified */ echo $block->getTabContent($_tab); ?> + <?= /* @noEscape */ $block->getAccordion($_tab); ?> </div> - </li> - <?php endforeach; ?> - </ul> - </div> - <?php endforeach; ?> -</div> - + </li> + <?php endforeach; ?> + </ul> + </div> + <?php endforeach; ?> + </div> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml new file mode 100644 index 0000000000000000000000000000000000000000..1d5e98a29e10bbcb56d091dc877a9f4ffe7ab722 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/edit/tabs/child_tab.phtml @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +// @codingStandardsIgnoreFile + +/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\ChildTab */ +?> +<div class="fieldset-wrapper admin__collapsible-block-wrapper" data-tab="<?= /* @noEscape */ $block->getTabId()?>" + id="<?= /* @noEscape */ $block->getTabId() ?>-wrapper" data-mage-init='{"collapsible":{ + "active": <?= /* @noEscape */ $block->isTabOpened() ? 'true' : 'false' ?>, + "openedState": "_show", + "closedState": "_hide", + "animate": 200, + "collapsible": true + }}' + > + <div class="fieldset-wrapper-title" data-role="title"> + <strong class="admin__collapsible-title" data-role="trigger" > + <span><?= /* @noEscape */ $block->getTitle()?></span> + </strong> + </div> + <div class="fieldset-wrapper-content" data-role="content" id="<?= /* @noEscape */ $block->getTabId()?>-content"> + <?= /* @noEscape */ $block->getContent()?> + </div> +</div> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js index 5ccfea334c792c0a5990a5b6f32398cb9b1ed520..163a537df655fc11d77b9da1e3702d12bf0534c1 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/base-image-uploader.js @@ -33,6 +33,9 @@ define([ var findElement = function (data) { return $container.find('.image:not(.image-placeholder)').filter(function () { + if(!$(this).data('image')) { + return false; + } return $(this).data('image').file === data.file; }).first(); }; diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/type-switcher.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/type-switcher.js index fb525cc6a5019d8e30ccf0c2cd5ec5423c986b3c..60fd9c6dbd8009ca9aaff7a9aacbff616ce28c3d 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/type-switcher.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/type-switcher.js @@ -4,7 +4,7 @@ */ define([ "jquery" -], function($){ +], function($) { /** * Type Switcher @@ -13,14 +13,21 @@ define([ * @constructor */ var TypeSwitcher = function (data) { - this._data = data; this.$type = $('#product_type_id'); this.$weight = $('#' + data.weight_id); - this.$is_virtual = $('#' + data.is_virtual_id); + this.$weight_switcher = $(data.weight_switcher); this.$tab = $('#' + data.tab_id); + this.productHasWeight = function () { + return $('input:checked', this.$weight_switcher).val() == data.product_has_weight_flag; + }; + this.notifyProductWeightIsChanged = function () { + return $('input:checked', this.$weight_switcher).trigger('change'); + }; + this.hideSwitcher = function () { + this.$weight_switcher.hide(); + }; - // @todo: move $is_virtual checkbox logic to separate widget - if (this.$is_virtual.is(':checked')) { + if (!this.productHasWeight()) { this.baseType = { virtual: this.$type.val(), real: 'simple' @@ -39,37 +46,44 @@ define([ * Bind event */ bindAll: function () { - var self = this, - $type = this.$type; - $type.on('change', function() { - self._switchToType($type.val()); - }); + this.$type.on('change', function() { + this._switchToType(this.$type.val()); + }.bind(this)); $('[data-form=edit-product] [data-role=tabs]').on('contentUpdated', function() { - self._switchToType($type.val()); - self.$is_virtual.trigger('change'); - }); + this._switchToType(this.$type.val()); + this.notifyProductWeightIsChanged(); + }.bind(this)); $("#product_info_tabs").on("beforePanelsMove tabscreate tabsactivate", function() { - self._switchToType($type.val()); - self.$is_virtual.trigger('change'); - }); + this._switchToType(this.$type.val()); + this.notifyProductWeightIsChanged(); + }.bind(this)); - this.$is_virtual.on('change click', function() { - if ($(this).is(':checked')) { - $type.val(self.baseType.virtual).trigger('change'); - if ($type.val() != 'bundle') { // @TODO move this check to Magento_Bundle after refactoring as widget - self.disableElement(self.$weight); - } - self.$tab.show().closest('li').removeClass('removed'); - } else { - $type.val(self.baseType.real).trigger('change'); - if ($type.val() != 'bundle') { // @TODO move this check to Magento_Bundle after refactoring as widget - self.enableElement(self.$weight); - } - self.$tab.hide(); + $(document).on('typeSwitcherOut', function () { + $('input:not(:checked)', this.$weight_switcher).trigger('click'); + }.bind(this)); + + $('input', this.$weight_switcher).on('change', this.changeType.bind(this)).trigger('change'); + }, + + changeType: function() { + $(document).trigger('typeSwitcher', { + hasWeight: this.productHasWeight() + }); + if (!this.productHasWeight()) { + this.$type.val(this.baseType.virtual).trigger('change'); + if (this.$type.val() != 'bundle') { // @TODO move this check to Magento_Bundle after refactoring as widget + this.disableElement(this.$weight); + } + this.$tab.show().closest('li').removeClass('removed'); + } else { + this.$type.val(this.baseType.real).trigger('change'); + if (this.$type.val() != 'bundle') { // @TODO move this check to Magento_Bundle after refactoring as widget + this.enableElement(this.$weight); } - }).trigger('change'); + this.$tab.hide(); + } }, /** @@ -133,7 +147,5 @@ define([ }); } }); - // export to global scope - window.TypeSwitcher = TypeSwitcher; - -}); \ No newline at end of file + return TypeSwitcher; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js new file mode 100644 index 0000000000000000000000000000000000000000..0734c4362c52842538493da8a6f9592ab3ffdaf6 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js @@ -0,0 +1,168 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable no-undef */ +// jscs:disable jsDoc + +define([ + 'jquery', + 'mage/template', + 'uiRegistry', + 'jquery/ui', + 'prototype' +], function (jQuery, mageTemplate, rg) { + 'use strict'; + + return function (config) { + var optionDefaultInputType = 'radio', + attributeOption = { + table: $('attribute-options-table'), + itemCount: 0, + totalItems: 0, + rendered: 0, + template: mageTemplate('#row-template'), + isReadOnly: config.idReadOnly, + add: function (data, render) { + var isNewOption = false, + element; + + if (typeof data.id == 'undefined') { + data = { + 'id': 'option_' + this.itemCount, + 'sort_order': this.itemCount + 1 + }; + isNewOption = true; + } + + if (!data.intype) { + data.intype = optionDefaultInputType; + } + element = this.template({ + data: data + }); + + if (isNewOption && !this.isReadOnly) { + this.enableNewOptionDeleteButton(data.id); + } + this.itemCount++; + this.totalItems++; + this.elements += element; + + if (render) { + this.render(); + } + }, + remove: function (event) { + var element = $(Event.findElement(event, 'tr')), + elementFlags; // !!! Button already have table parent in safari + + // Safari workaround + element.ancestors().each(function (parentItem) { + if (parentItem.hasClassName('option-row')) { + element = parentItem; + throw $break; + } else if (parentItem.hasClassName('box')) { + throw $break; + } + }); + + if (element) { + elementFlags = element.getElementsByClassName('delete-flag'); + + if (elementFlags[0]) { + elementFlags[0].value = 1; + } + + element.addClassName('no-display'); + element.addClassName('template'); + element.hide(); + this.totalItems--; + this.updateItemsCountField(); + } + }, + updateItemsCountField: function () { + $('option-count-check').value = this.totalItems > 0 ? '1' : ''; + }, + enableNewOptionDeleteButton: function (id) { + $$('#delete_button_container_' + id + ' button').each(function (button) { + button.enable(); + button.removeClassName('disabled'); + }); + }, + bindRemoveButtons: function () { + jQuery('#swatch-visual-options-panel').on('click', '.delete-option', this.remove.bind(this)); + }, + render: function () { + Element.insert($$('[data-role=options-container]')[0], this.elements); + this.elements = ''; + }, + renderWithDelay: function (data, from, step, delay) { + var arrayLength = data.length, + len; + + for (len = from + step; from < len && from < arrayLength; from++) { + this.add(data[from]); + } + this.render(); + + if (from === arrayLength) { + this.updateItemsCountField(); + this.rendered = 1; + jQuery('body').trigger('processStop'); + + return true; + } + setTimeout(this.renderWithDelay.bind(this, data, from, step, delay), delay); + }, + ignoreValidate: function () { + var ignore = '.ignore-validate input, ' + + '.ignore-validate select, ' + + '.ignore-validate textarea'; + + jQuery('#edit_form').data('validator').settings.forceIgnore = ignore; + } + }; + + if ($('add_new_option_button')) { + Event.observe('add_new_option_button', 'click', attributeOption.add.bind(attributeOption, {}, true)); + } + + $('manage-options-panel').on('click', '.delete-option', function (event) { + attributeOption.remove(event); + }); + + jQuery('#manage-options-panel').on('render', function () { + attributeOption.ignoreValidate(); + + if (attributeOption.rendered) { + return false; + } + jQuery('body').trigger('processStart'); + attributeOption.renderWithDelay(config.attributesData, 0, 100, 300); + attributeOption.bindRemoveButtons(); + }); + + if (config.isSortable) { + jQuery(function ($) { + $('[data-role=options-container]').sortable({ + distance: 8, + tolerance: 'pointer', + cancel: 'input, button', + axis: 'y', + update: function () { + $('[data-role=options-container] [data-role=order]').each(function (index, element) { + $(element).val(index + 1); + }); + } + }); + }); + } + + window.attributeOption = attributeOption; + window.optionDefaultInputType = optionDefaultInputType; + + rg.set('manage-options-panel', attributeOption); + }; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/product-gallery.js b/app/code/Magento/Catalog/view/adminhtml/web/js/product-gallery.js index 50790af825daeddb5ce55a99c2b4865d704b572b..890dd8d6b456f6b3df6cf0b69609bd78f102dc6b 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/product-gallery.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/product-gallery.js @@ -52,21 +52,26 @@ define([ setPosition: '_setPosition', resort: '_resort', 'mouseup [data-role=delete-button]': function (event) { + var $imageContainer; + event.preventDefault(); - var $imageContainer = $(event.currentTarget).closest(this.options.imageSelector); + $imageContainer = $(event.currentTarget).closest(this.options.imageSelector); this.element.find('[data-role=dialog]').trigger('close'); this.element.trigger('removeItem', $imageContainer.data('imageData')); }, 'mouseup [data-role=make-base-button]': function (event) { + var $imageContainer, + imageData; + event.preventDefault(); event.stopImmediatePropagation(); - var $imageContainer = $(event.currentTarget).closest(this.options.imageSelector); - var imageData = $imageContainer.data('imageData'); + $imageContainer = $(event.currentTarget).closest(this.options.imageSelector); + imageData = $imageContainer.data('imageData'); this.setBase(imageData); } }; - this._on(events); + this._on(events); this.element.sortable({ distance: 8, items: this.options.imageSelector, @@ -90,7 +95,7 @@ define([ return el; }), function (el) { - return el.value == baseImage.value; + return el.value === baseImage.value; } ); var isImageOpened = this.findElement(imageData).hasClass('active'); @@ -117,7 +122,7 @@ define([ */ findElement: function (data) { return this.element.find(this.options.imageSelector).filter(function () { - return $(this).data('imageData').file == data.file; + return $(this).data('imageData').file === data.file; }).first(); }, @@ -128,31 +133,38 @@ define([ * @private */ _addItem: function (event, imageData) { - var count = this.element.find(this.options.imageSelector).length; + var count = this.element.find(this.options.imageSelector).length, + element; + imageData = $.extend({ file_id: Math.random().toString(33).substr(2, 18), disabled: 0, position: count + 1 }, imageData); - var element = this.imgTmpl({ + element = this.imgTmpl({ data: imageData }); element = $(element).data('imageData', imageData); + if(imageData.subclass) { + element.addClass(imageData.subclass); + } if (count === 0) { element.prependTo(this.element); } else { element.insertAfter(this.element.find(this.options.imageSelector + ':last')); } - if (!this.options.initialized && this.options.images.length === 0 || - this.options.initialized && this.element.find(this.options.imageSelector + ':not(.removed)').length == 1 + if (!this.options.initialized && + this.options.images.length === 0 || + this.options.initialized && + this.element.find(this.options.imageSelector + ':not(.removed)').length === 1 ) { this.setBase(imageData); } $.each(this.options.types, $.proxy(function (index, image) { - if (imageData.file == image.value) { + if (imageData.file === image.value) { this.element.trigger('setImageType', { type: image.code, imageData: imageData @@ -269,6 +281,8 @@ define([ events['mouseup ' + this.options.imageSelector] = function (event) { if (!$(event.currentTarget).is('.ui-sortable-helper')) { $(event.currentTarget).addClass('active'); + var itemId = $(event.currentTarget).find('input')[0].name.match(/\[([^\]]*)\]/g)[2]; + $('#item_id').val(itemId); this._showDialog($(event.currentTarget).data('imageData')); } }; @@ -311,6 +325,12 @@ define([ */ _showDialog: function (imageData) { var $imageContainer = this.findElement(imageData); + + if ($imageContainer.find('input[name*="media_type"]').val() == 'external-video') { + $('#new-video').modal('openModal'); + return; + } + var dialogElement = $imageContainer.data('dialog'); if ($imageContainer.is('.removed') || (dialogElement && dialogElement.is(':visible'))) { diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js b/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js new file mode 100644 index 0000000000000000000000000000000000000000..92b800acd25c6bd9f8b787a52a58e4253d278665 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js @@ -0,0 +1,20 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + return { + + /** + * Hide weight switcher + */ + hideWeightSwitcher: function () { + $('[data-role=weight-switcher]').hide(); + } + }; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/product/product.css b/app/code/Magento/Catalog/view/adminhtml/web/product/product.css index 443d6ebc0375c4324b86a5cdc6c42c4d26ea07e0..d425b8d20ac8f6717702a055d0af55b0e6cf0958 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/product/product.css +++ b/app/code/Magento/Catalog/view/adminhtml/web/product/product.css @@ -231,11 +231,11 @@ width: 140px; margin-right: 15px; } - +/* .admin__scope-old .field-quantity_and_stock_status select { vertical-align: middle; } - +*/ /* Weight field */ .admin__scope-old .field-weight .control { display: table; diff --git a/app/code/Magento/Catalog/view/base/layout/catalog_product_prices.xml b/app/code/Magento/Catalog/view/base/layout/catalog_product_prices.xml index 8e700eac76263940857cfe11f1d86af2de586d00..135f43cdce6d9cd07f01de92904d6721f86db2d4 100644 --- a/app/code/Magento/Catalog/view/base/layout/catalog_product_prices.xml +++ b/app/code/Magento/Catalog/view/base/layout/catalog_product_prices.xml @@ -17,9 +17,6 @@ <item name="special_price" xsi:type="array"> <item name="render_template" xsi:type="string">Magento_Catalog::product/price/special_price.phtml</item> </item> - <item name="group_price" xsi:type="array"> - <item name="render_template" xsi:type="string">Magento_Catalog::product/price/group_price.phtml</item> - </item> <item name="tier_price" xsi:type="array"> <item name="render_template" xsi:type="string">Magento_Catalog::product/price/tier_prices.phtml</item> </item> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml index f995ee821416909339082cd2e9a6de29914e0891..0360288380c3bcfa41eb83b7dcbedf677869fea5 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto.phtml @@ -18,13 +18,11 @@ $compareHelper = $this->helper('Magento\Catalog\Helper\Product\Compare'); <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow()) : ?> <a href="#" class="action towishlist" - title="<?php /* @escapeNotVerified */ echo __('Add to Wish List') ?>" data-post='<?php /* @escapeNotVerified */ echo $_wishlistSubmitParams; ?>' data-action="add-to-wishlist"><span><?php /* @escapeNotVerified */ echo __('Add to Wish List') ?></span></a> <?php endif; ?> <a href="#" data-post='<?php /* @escapeNotVerified */ echo $compareHelper->getPostDataParams($_product);?>' data-role="add-to-links" - title="<?php /* @escapeNotVerified */ echo __('Add to Compare') ?>" class="action tocompare"><span><?php /* @escapeNotVerified */ echo __('Add to Compare') ?></span></a> </div> <script type="text/x-magento-init"> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml index 1f623aab1008d217ec204a914620c913c96bec95..b7879d7cf49e9d625641ffef7f3013161d6114ed 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml @@ -12,7 +12,7 @@ <?php $buttonTitle = __('Add to Cart'); ?> <?php if ($_product->isSaleable()): ?> <div class="box-tocart"> - <fieldset class="fieldset"> + <div class="fieldset"> <?php if ($block->shouldRenderQuantity()): ?> <div class="field qty"> <label class="label" for="qty"><span><?php /* @escapeNotVerified */ echo __('Qty') ?></span></label> @@ -37,7 +37,7 @@ </button> <?php echo $block->getChildHtml('', true) ?> </div> - </fieldset> + </div> </div> <?php endif; ?> <?php if ($block->isRedirectToCartEnabled()) : ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml index 1280316a54a8fc05ad17ddb23fed9013751a7e65..fc81da5f39ffe85b3ffede2077f67b1a6c670b56 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/mailto.phtml @@ -9,7 +9,6 @@ ?> <?php $_product = $block->getProduct() ?> <?php if ($block->canEmailToFriend()): ?> - <a title="<?php /* @escapeNotVerified */ echo __('Email') ?>" - href="<?php /* @escapeNotVerified */ echo $this->helper('Magento\Catalog\Helper\Product')->getEmailToFriendUrl($_product) ?>" + <a href="<?php /* @escapeNotVerified */ echo $this->helper('Magento\Catalog\Helper\Product')->getEmailToFriendUrl($_product) ?>" class="action mailto friend"><span><?php /* @escapeNotVerified */ echo __('Email') ?></span></a> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml index 6a759a9def0e6efd3a3591927f17cbfea50f9064..925268c9d9bcc17d08d6c215ee4550bb866ab899 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/wrapper.phtml @@ -11,7 +11,7 @@ if ($block->hasRequiredOptions()) { } ?> <div class="product-options-wrapper" id="product-options-wrapper"<?php /* @escapeNotVerified */ echo $required; ?>> - <fieldset class="fieldset" tabindex="0"> + <div class="fieldset" tabindex="0"> <?php echo $block->getChildHtml('', true);?> - </fieldset> + </div> </div> diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 0377e0ca6382785170bcb0541fce28f38d1ca4cd..9610475c7ded6ae30f9d85d62bab8810e779d9c2 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -476,21 +476,28 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity return []; } $select = $this->_connection->select()->from( + ['mgvte' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value_to_entity')], + [ + 'mgvte.entity_id', + 'mgvte.value_id' + ] + )->joinLeft( ['mg' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery')], + '(mg.value_id = mgvte.value_id)', [ - 'mg.entity_id', 'mg.attribute_id', 'filename' => 'mg.value', - 'mgv.label', - 'mgv.position', - 'mgv.disabled' ] )->joinLeft( ['mgv' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value')], '(mg.value_id = mgv.value_id AND mgv.store_id = 0)', - [] + [ + 'mgv.label', + 'mgv.position', + 'mgv.disabled' + ] )->where( - 'mg.entity_id IN(?)', + 'mgvte.entity_id IN(?)', $productIds ); diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product/Type/Simple.php b/app/code/Magento/CatalogImportExport/Model/Export/Product/Type/Simple.php index 5b42a3b6e1913d05bf869486d374cda5de1f4bb6..dba9af98c9df73999758e207ad6145ff1d83be21 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product/Type/Simple.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product/Type/Simple.php @@ -32,7 +32,6 @@ class Simple extends \Magento\CatalogImportExport\Model\Export\Product\Type\Abst protected $_disabledAttrs = [ 'old_id', 'tier_price', - 'group_price', 'category_ids', ]; } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index dc2fefaa9f83e2b2d7135bfc7edec70274a4a20b..92436b16575d93dd9146777176b5a2db590b4c61 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -308,9 +308,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity '_tier_price_qty', '_tier_price_price', '_related_sku', - '_group_price_website', - '_group_price_customer_group', - '_group_price_price', '_related_position', '_crosssell_sku', '_crosssell_position', @@ -1286,12 +1283,18 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity if (!$productMediaGalleryTableName) { $productMediaGalleryTableName = $resource->getTable('catalog_product_entity_media_gallery'); } - $allMedia = $this->_connection->fetchAll($this->_connection->select() - ->from( - $productMediaGalleryTableName, - ['entity_id', 'value'] - )->where('value IN (?)', $allImagesFromBunch) + $select = $this->_connection->select()->from( + ['mg' => $resource->getTable('catalog_product_entity_media_gallery')], + ['value' => 'mg.value'] + )->joinLeft( + ['mgvte' => $resource->getTable('catalog_product_entity_media_gallery_value_to_entity')], + '(mg.value_id = mgvte.value_id)', + ['entity_id' => 'mgvte.entity_id'] + )->where( + 'mg.value IN(?)', + $allImagesFromBunch ); + $allMedia = $this->_connection->fetchAll($select); $result = array(); foreach ($allMedia as $image) { $result[$image['value']] = []; @@ -1329,7 +1332,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $this->websitesCache = []; $this->categoriesCache = []; $tierPrices = []; - $groupPrices = []; $mediaGallery = []; $uploadedGalleryFiles = []; $previousType = null; @@ -1417,18 +1419,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity ]; } - // 4.2. Group prices phase - if (!empty($rowData['_group_price_website'])) { - $groupPrices[$rowSku][] = [ - 'all_groups' => $rowData['_group_price_customer_group'] == self::VALUE_ALL, - 'customer_group_id' => $rowData['_group_price_customer_group'] == - self::VALUE_ALL ? 0 : $rowData['_group_price_customer_group'], - 'value' => $rowData['_group_price_price'], - 'website_id' => self::VALUE_ALL == $rowData['_group_price_website'] || - $priceIsGlobal ? 0 : $this->storeResolver->getWebsiteCodeToId($rowData['_group_price_website']), - ]; - } - if (!$this->validateRow($rowData, $rowNum)) { continue; } @@ -1618,8 +1608,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $this->categoriesCache )->_saveProductTierPrices( $tierPrices - )->_saveProductGroupPrices( - $groupPrices )->_saveMediaGallery( $mediaGallery )->_saveProductAttributes( @@ -1703,45 +1691,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity return $this; } - /** - * Save product group prices. - * - * @param array $groupPriceData - * @return $this - */ - protected function _saveProductGroupPrices(array $groupPriceData) - { - static $tableName = null; - - if (!$tableName) { - $tableName = $this->_resourceFactory->create()->getTable('catalog_product_entity_group_price'); - } - if ($groupPriceData) { - $groupPriceIn = []; - $delProductId = []; - - foreach ($groupPriceData as $delSku => $groupPriceRows) { - $productId = $this->skuProcessor->getNewSku($delSku)['entity_id']; - $delProductId[] = $productId; - - foreach ($groupPriceRows as $row) { - $row['entity_id'] = $productId; - $groupPriceIn[] = $row; - } - } - if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior()) { - $this->_connection->delete( - $tableName, - $this->_connection->quoteInto('entity_id IN (?)', $delProductId) - ); - } - if ($groupPriceIn) { - $this->_connection->insertOnDuplicate($tableName, $groupPriceIn, ['value']); - } - } - return $this; - } - /** * Returns an object for upload a media files * @@ -1814,29 +1763,33 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity if (empty($mediaGalleryData)) { return $this; } - static $mediaGalleryTableName = null; static $mediaValueTableName = null; + static $mediaEntityToValueTableName = null; static $productId = null; - if (!$mediaGalleryTableName) { $mediaGalleryTableName = $this->_resourceFactory->create()->getTable( 'catalog_product_entity_media_gallery' ); } - if (!$mediaValueTableName) { $mediaValueTableName = $this->_resourceFactory->create()->getTable( 'catalog_product_entity_media_gallery_value' ); } + if (!$mediaEntityToValueTableName) { + $mediaEntityToValueTableName = $this->_resourceFactory->create()->getTable( + 'catalog_product_entity_media_gallery_value_to_entity' + ); + } $productIds = []; + $imageNames = []; $multiInsertData = []; + $valueToProductId = []; foreach ($mediaGalleryData as $productSku => $mediaGalleryRows) { $productId = $this->skuProcessor->getNewSku($productSku)['entity_id']; $productIds[] = $productId; $insertedGalleryImgs = []; - if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior()) { $this->_connection->delete( $mediaGalleryTableName, @@ -1847,31 +1800,28 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity if (!in_array($insertValue['value'], $insertedGalleryImgs)) { $valueArr = [ 'attribute_id' => $insertValue['attribute_id'], - 'entity_id' => $productId, 'value' => $insertValue['value'], ]; + $valueToProductId[$insertValue['value']] = $productId; + $imageNames = $insertValue['value']; $multiInsertData[] = $valueArr; $insertedGalleryImgs[] = $insertValue['value']; } } } - $this->_connection->insertOnDuplicate($mediaGalleryTableName, $multiInsertData, ['entity_id']); + $this->_connection->insertOnDuplicate($mediaGalleryTableName, $multiInsertData, []); $multiInsertData = []; + $dataForSkinnyTable = []; $newMediaValues = $this->_connection->fetchAssoc( - $this->_connection->select()->from( - $mediaGalleryTableName, - ['value_id', 'value', 'entity_id'] - )->where( - 'entity_id IN (?)', - $productIds - ) + $this->_connection->select()->from($mediaGalleryTableName, ['value_id', 'value']) + ->where('value IN (?)', $imageNames) ); foreach ($mediaGalleryData as $productSku => $mediaGalleryRows) { foreach ($mediaGalleryRows as $insertValue) { foreach ($newMediaValues as $value_id => $values) { if ($values['value'] == $insertValue['value']) { $insertValue['value_id'] = $value_id; - $insertValue['entity_id'] = $values['entity_id']; + $insertValue['entity_id'] = $valueToProductId[$values['value']]; unset($newMediaValues[$value_id]); break; } @@ -1886,18 +1836,22 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity 'disabled' => $insertValue['disabled'], ]; $multiInsertData[] = $valueArr; + $dataForSkinnyTable[] = [ + 'value_id' => $insertValue['value_id'], + 'entity_id' => $insertValue['entity_id'], + ]; } } } try { $this->_connection->insertOnDuplicate($mediaValueTableName, $multiInsertData, ['value_id']); + $this->_connection->insertOnDuplicate($mediaEntityToValueTableName, $dataForSkinnyTable, ['value_id']); } catch (\Exception $e) { $this->_connection->delete( $mediaGalleryTableName, $this->_connection->quoteInto('value_id IN (?)', $newMediaValues) ); } - return $this; } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php index c639fcc4acf25d20c9ac40e7d664772e1c797827..916775c4152c7c30dfa15436dfa1dcce1d18ecf7 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php @@ -47,12 +47,6 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn const ERROR_TIER_DATA_INCOMPLETE = 'tierPriceDataIsIncomplete'; - const ERROR_INVALID_GROUP_PRICE_SITE = 'groupPriceWebsiteInvalid'; - - const ERROR_INVALID_GROUP_PRICE_GROUP = 'groupPriceGroupInvalid'; - - const ERROR_GROUP_PRICE_DATA_INCOMPLETE = 'groupPriceDataIsIncomplete'; - const ERROR_SKU_NOT_FOUND_FOR_DELETE = 'skuNotFoundToDelete'; const ERROR_SUPER_PRODUCTS_SKU_NOT_FOUND = 'superProductsSkuNotFound'; diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/GroupPrice.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/GroupPrice.php deleted file mode 100644 index 56d6aebd0441b6c9f1118d264b9210a72e5df490..0000000000000000000000000000000000000000 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator/GroupPrice.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\CatalogImportExport\Model\Import\Product\Validator; - -use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface; - -class GroupPrice extends AbstractPrice implements RowValidatorInterface -{ - /** - * @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver - */ - protected $storeResolver; - - /** - * @param \Magento\Customer\Api\GroupRepositoryInterface $groupRepository - * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder - * @param \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver - */ - public function __construct( - \Magento\Customer\Api\GroupRepositoryInterface $groupRepository, - \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder, - \Magento\CatalogImportExport\Model\Import\Product\StoreResolver $storeResolver - ) { - $this->storeResolver = $storeResolver; - parent::__construct($groupRepository, $searchCriteriaBuilder); - } - - /** - * {@inheritdoc} - */ - public function init($context) - { - return parent::init($context); - } - - /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function isValid($value) - { - $this->_clearMessages(); - if (isset( - $value['_group_price_website'] - ) && strlen( - $value['_group_price_website'] - ) || isset( - $value['_group_price_customer_group'] - ) && strlen( - $value['_group_price_customer_group'] - ) || isset( - $value['_group_price_price'] - ) && strlen( - $value['_group_price_price'] - ) - ) { - if (!isset( - $value['_group_price_website'] - ) || !isset( - $value['_group_price_customer_group'] - ) || !strlen( - $value['_group_price_website'] - ) || !strlen( - $value['_group_price_customer_group'] - ) || !strlen( - $value['_group_price_price'] - ) - ) { - $this->_addMessages([self::ERROR_GROUP_PRICE_DATA_INCOMPLETE]); - return false; - } elseif ($value['_group_price_website'] != self::VALUE_ALL - && !$this->storeResolver->getWebsiteCodeToId($value['_group_price_website']) - ) { - $this->_addMessages([self::ERROR_INVALID_GROUP_PRICE_SITE]); - return false; - } elseif ($value['_group_price_customer_group'] != self::VALUE_ALL && !isset( - $this->customerGroups[$value['_group_price_customer_group']] - ) - ) { - $this->_addMessages([self::ERROR_INVALID_GROUP_PRICE_GROUP]); - return false; - } - } - return true; - } -} diff --git a/app/code/Magento/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml index 55daa1e2d6f1f8c2820946fb0429490edf2dcc44..1af0ab15f61444d0b9ddec9dd3c3e08ebb1b0bbd 100644 --- a/app/code/Magento/CatalogImportExport/etc/di.xml +++ b/app/code/Magento/CatalogImportExport/etc/di.xml @@ -18,7 +18,6 @@ <type name="Magento\CatalogImportExport\Model\Import\Product\Validator"> <arguments> <argument name="validators" xsi:type="array"> - <item name="groupPrice" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\GroupPrice</item> <item name="media" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\Media</item> <item name="superProductsSku" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\SuperProductsSku</item> <item name="tierPrice" xsi:type="object">Magento\CatalogImportExport\Model\Import\Product\Validator\TierPrice</item> diff --git a/app/code/Magento/CatalogInventory/Helper/Stock.php b/app/code/Magento/CatalogInventory/Helper/Stock.php index 0db023cf26eb37eb3736c31bccb8e12c399015ef..a30eac244c637f7b7dd1905215223f98e2e59f1c 100644 --- a/app/code/Magento/CatalogInventory/Helper/Stock.php +++ b/app/code/Magento/CatalogInventory/Helper/Stock.php @@ -5,21 +5,19 @@ */ namespace Magento\CatalogInventory\Helper; -use Magento\CatalogInventory\Api\StockRegistryInterface; +use Magento\CatalogInventory\Model\Spi\StockRegistryProviderInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\ObjectManagerInterface; +use Magento\CatalogInventory\Model\Resource\Stock\StatusFactory; +use Magento\CatalogInventory\Model\Resource\Stock\Status; +use Magento\Catalog\Model\Resource\Collection\AbstractCollection; +use Magento\Catalog\Model\Product; /** * Class Stock */ class Stock { - /** - * @var StockRegistryInterface - */ - protected $stockRegistry; - /** * Store model manager * @@ -35,45 +33,50 @@ class Stock protected $scopeConfig; /** - * @var \Magento\CatalogInventory\Model\Resource\Stock\Status + * @var Status */ protected $stockStatusResource; /** - * @var \Magento\Framework\ObjectManagerInterface + * @var StatusFactory + */ + protected $stockStatusFactory; + + /** + * @var StockRegistryProviderInterface */ - protected $objectManger; + private $stockRegistryProvider; /** - * @param StockRegistryInterface $stockRegistry * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $scopeConfig - * @param ObjectManagerInterface $objectManager + * @param StatusFactory $stockStatusFactory + * @param StockRegistryProviderInterface $stockRegistryProvider */ public function __construct( - StockRegistryInterface $stockRegistry, StoreManagerInterface $storeManager, ScopeConfigInterface $scopeConfig, - ObjectManagerInterface $objectManager + StatusFactory $stockStatusFactory, + StockRegistryProviderInterface $stockRegistryProvider ) { - $this->stockRegistry = $stockRegistry; $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; - $this->objectManger = $objectManager; + $this->stockStatusFactory = $stockStatusFactory; + $this->stockRegistryProvider = $stockRegistryProvider; } /** * Assign stock status information to product * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @param int $stockStatus * @return void */ - public function assignStatusToProduct(\Magento\Catalog\Model\Product $product, $stockStatus = null) + public function assignStatusToProduct(Product $product, $stockStatus = null) { if ($stockStatus === null) { $websiteId = $product->getStore()->getWebsiteId(); - $stockStatus = $this->stockRegistry->getStockStatus($product->getId(), $websiteId); + $stockStatus = $this->stockRegistryProvider->getStockStatus($product->getId(), $websiteId); $status = $stockStatus->getStockStatus(); } $product->setIsSalable($status); @@ -82,18 +85,15 @@ class Stock /** * Add stock status information to products * - * @param \Magento\Catalog\Model\Resource\Collection\AbstractCollection $productCollection + * @param AbstractCollection $productCollection * @return void - * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - public function addStockStatusToProducts( - \Magento\Catalog\Model\Resource\Collection\AbstractCollection $productCollection - ) { + public function addStockStatusToProducts(AbstractCollection $productCollection) + { $websiteId = $this->storeManager->getStore($productCollection->getStoreId())->getWebsiteId(); - $productIds = []; foreach ($productCollection as $product) { $productId = $product->getId(); - $stockStatus = $this->stockRegistry->getStockStatus($productId, $websiteId); + $stockStatus = $this->stockRegistryProvider->getStockStatus($productId, $websiteId); $status = $stockStatus->getStockStatus(); $product->setIsSalable($status); } @@ -131,19 +131,6 @@ class Stock ); } - /** - * Add stock status to prepare index select - * - * @param \Magento\Framework\DB\Select $select - * @param \Magento\Store\Model\Website $website - * @return void - */ - public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Magento\Store\Model\Website $website) - { - $resource = $this->getStockStatusResource(); - $resource->addStockStatusToSelect($select, $website); - } - /** * Add only is in stock products filter to product collection * @@ -157,14 +144,12 @@ class Stock } /** - * @return \Magento\CatalogInventory\Model\Resource\Stock\Status + * @return Status */ protected function getStockStatusResource() { if (empty($this->stockStatusResource)) { - $this->stockStatusResource = $this->objectManger->get( - 'Magento\CatalogInventory\Model\Resource\Stock\Status' - ); + $this->stockStatusResource = $this->stockStatusFactory->create(); } return $this->stockStatusResource; } diff --git a/app/code/Magento/CatalogInventory/Model/Resource/QtyCounterInterface.php b/app/code/Magento/CatalogInventory/Model/Resource/QtyCounterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e36a04a2b3d40630ee819410f7f86aa4c283f6dd --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/Resource/QtyCounterInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogInventory\Model\Resource; + +/** + * Correct particular stock products qty + */ +interface QtyCounterInterface +{ + /** + * Correct particular stock products qty based on operator + * + * @param int[] $items + * @param int $websiteId + * @param string $operator +/- + * @return void + */ + public function correctItemsQty(array $items, $websiteId, $operator); +} diff --git a/app/code/Magento/CatalogInventory/Model/Resource/Stock.php b/app/code/Magento/CatalogInventory/Model/Resource/Stock.php index e5c9c696404fb1dcd6b1a096be6190f09b16822f..6948b642136d19844f92273987dcf9c8ee3b299d 100644 --- a/app/code/Magento/CatalogInventory/Model/Resource/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/Resource/Stock.php @@ -12,7 +12,7 @@ use Magento\Store\Model\StoreManagerInterface; /** * Stock resource model */ -class Stock extends \Magento\Framework\Model\Resource\Db\AbstractDb +class Stock extends \Magento\Framework\Model\Resource\Db\AbstractDb implements QtyCounterInterface { /** * @var StockConfigurationInterface @@ -134,14 +134,9 @@ class Stock extends \Magento\Framework\Model\Resource\Db\AbstractDb } /** - * Correct particular stock products qty based on operator - * - * @param array $items - * @param int $websiteId - * @param string $operator +/- - * @return $this + * {@inheritdoc} */ - public function correctItemsQty(array $items, $websiteId, $operator = '-') + public function correctItemsQty(array $items, $websiteId, $operator) { if (empty($items)) { return $this; @@ -161,8 +156,6 @@ class Stock extends \Magento\Framework\Model\Resource\Db\AbstractDb $connection->beginTransaction(); $connection->update($this->getTable('cataloginventory_stock_item'), ['qty' => $value], $where); $connection->commit(); - - return $this; } /** diff --git a/app/code/Magento/CatalogInventory/Model/StockManagement.php b/app/code/Magento/CatalogInventory/Model/StockManagement.php index 3d5fee6d9cef90eb8bfcf98d78b6c45e68c0bca2..c487ab15394e79dce91848c9fa45dafddef19161 100644 --- a/app/code/Magento/CatalogInventory/Model/StockManagement.php +++ b/app/code/Magento/CatalogInventory/Model/StockManagement.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\ProductFactory; use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Api\StockManagementInterface; +use Magento\CatalogInventory\Model\Resource\QtyCounterInterface; use Magento\CatalogInventory\Model\Spi\StockRegistryProviderInterface; use Magento\Catalog\Api\ProductRepositoryInterface; @@ -38,26 +39,37 @@ class StockManagement implements StockManagementInterface protected $productRepository; /** - * @var \Magento\CatalogInventory\Model\Resource\Stock + * @var Resource\Stock */ protected $resource; /** + * @var QtyCounterInterface + */ + private $qtyCounter; + + /** + * @param Resource\Stock $stockResource * @param StockRegistryProviderInterface $stockRegistryProvider * @param StockState $stockState * @param StockConfigurationInterface $stockConfiguration * @param ProductRepositoryInterface $productRepository + * @param Resource\QtyCounterInterface $qtyCounter */ public function __construct( + Resource\Stock $stockResource, StockRegistryProviderInterface $stockRegistryProvider, StockState $stockState, StockConfigurationInterface $stockConfiguration, - ProductRepositoryInterface $productRepository + ProductRepositoryInterface $productRepository, + QtyCounterInterface $qtyCounter ) { $this->stockRegistryProvider = $stockRegistryProvider; $this->stockState = $stockState; $this->stockConfiguration = $stockConfiguration; $this->productRepository = $productRepository; + $this->qtyCounter = $qtyCounter; + $this->resource = $stockResource; } /** @@ -108,7 +120,7 @@ class StockManagement implements StockManagementInterface $fullSaveItems[] = $stockItem; } } - $this->getResource()->correctItemsQty($registeredItems, $websiteId, '-'); + $this->qtyCounter->correctItemsQty($registeredItems, $websiteId, '-'); $this->getResource()->commit(); return $fullSaveItems; } @@ -123,7 +135,7 @@ class StockManagement implements StockManagementInterface //if (!$websiteId) { $websiteId = $this->stockConfiguration->getDefaultWebsiteId(); //} - $this->getResource()->correctItemsQty($items, $websiteId, '+'); + $this->qtyCounter->correctItemsQty($items, $websiteId, '+'); return true; } @@ -168,15 +180,10 @@ class StockManagement implements StockManagementInterface } /** - * @return \Magento\CatalogInventory\Model\Resource\Stock + * @return Stock */ protected function getResource() { - if (empty($this->resource)) { - $this->resource = \Magento\Framework\App\ObjectManager::getInstance()->get( - 'Magento\CatalogInventory\Model\Resource\Stock' - ); - } return $this->resource; } diff --git a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php index 789fc3748dcd9a32b78b5cd3333020992c4d0e71..653f77000c8c45be8f4333f02d42b0501b5f9f3d 100644 --- a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php @@ -8,6 +8,7 @@ namespace Magento\CatalogInventory\Observer; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Api\StockIndexInterface; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\Framework\Event\Observer as EventObserver; @@ -32,7 +33,7 @@ class SaveInventoryDataObserver protected $stockRegistry; /** - * @var \Magento\CatalogInventory\Api\StockItemRepositoryInterface + * @var StockItemRepositoryInterface */ protected $stockItemRepository; @@ -74,13 +75,13 @@ class SaveInventoryDataObserver * @param StockIndexInterface $stockIndex * @param StockConfigurationInterface $stockConfiguration * @param StockRegistryInterface $stockRegistry - * @param \Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository + * @param StockItemRepositoryInterface $stockItemRepository */ public function __construct( StockIndexInterface $stockIndex, StockConfigurationInterface $stockConfiguration, StockRegistryInterface $stockRegistry, - \Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository + StockItemRepositoryInterface $stockItemRepository ) { $this->stockIndex = $stockIndex; $this->stockConfiguration = $stockConfiguration; diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php index f05a451220a2b908bb9d12c7f4ceb94e1dfb6c18..376bffe2c5d870e2c5e0952b5dcc9376040e0aa5 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Helper/StockTest.php @@ -18,34 +18,29 @@ class StockTest extends \PHPUnit_Framework_TestCase protected $stock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\CatalogInventory\Model\Spi\StockRegistryProviderInterface */ - protected $stockRegistryMock; + protected $stockRegistryProviderMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\StoreManagerInterface */ protected $storeManagerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\Config\ScopeConfigInterface */ protected $scopeConfigMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\CatalogInventory\Model\Resource\Stock\StatusFactory */ - protected $productFactoryMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; + protected $statusFactoryMock; protected function setUp() { - $this->stockRegistryMock = $this->getMockBuilder( - 'Magento\CatalogInventory\Api\StockRegistryInterface' + $this->stockRegistryProviderMock = $this->getMockBuilder( + 'Magento\CatalogInventory\Model\Spi\StockRegistryProviderInterface' ) ->disableOriginalConstructor() ->getMock(); @@ -55,14 +50,15 @@ class StockTest extends \PHPUnit_Framework_TestCase $this->scopeConfigMock = $this->getMockBuilder('Magento\Framework\App\Config\ScopeConfigInterface') ->disableOriginalConstructor() ->getMock(); - $this->objectManagerMock = $this->getMockBuilder('Magento\Framework\App\ObjectManager') + $this->statusFactoryMock = $this->getMockBuilder('Magento\CatalogInventory\Model\Resource\Stock\StatusFactory') ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); $this->stock = new Stock( - $this->stockRegistryMock, $this->storeManagerMock, $this->scopeConfigMock, - $this->objectManagerMock + $this->statusFactoryMock, + $this->stockRegistryProviderMock ); } @@ -77,7 +73,7 @@ class StockTest extends \PHPUnit_Framework_TestCase $stockStatusMock->expects($this->any()) ->method('getStockStatus') ->willReturn($status); - $this->stockRegistryMock->expects($this->any()) + $this->stockRegistryProviderMock->expects($this->any()) ->method('getStockStatus') ->willReturn($stockStatusMock); $storeMock = $this->getMockBuilder('Magento\Store\Model\Store') @@ -142,7 +138,7 @@ class StockTest extends \PHPUnit_Framework_TestCase $this->storeManagerMock->expects($this->once()) ->method('getStore') ->willReturn($storeMock); - $this->stockRegistryMock->expects($this->once()) + $this->stockRegistryProviderMock->expects($this->once()) ->method('getStockStatus') ->withAnyParameters() ->willReturn($stockStatusMock); @@ -177,28 +173,6 @@ class StockTest extends \PHPUnit_Framework_TestCase ]; } - public function testAddStockStatusToSelect() - { - $selectMock = $this->getMockBuilder('Magento\Framework\DB\Select') - ->disableOriginalConstructor() - ->getMock(); - $websiteMock = $this->getMockBuilder('Magento\Store\Model\Website') - ->disableOriginalConstructor() - ->getMock(); - $stockStatusMock = $this->getMockBuilder('Magento\CatalogInventory\Model\Resource\Stock\Status') - ->disableOriginalConstructor() - ->setMethods(['addStockStatusToSelect']) - ->getMock(); - $stockStatusMock->expects($this->once()) - ->method('addStockStatusToSelect') - ->with($selectMock, $websiteMock); - $this->objectManagerMock->expects($this->once()) - ->method('get') - ->willReturn($stockStatusMock); - - $this->assertNull($this->stock->addStockStatusToSelect($selectMock, $websiteMock)); - } - public function testAddIsInStockFilterToCollection() { $collectionMock = $this->getMockBuilder('Magento\Catalog\Model\Resource\Product\Collection') @@ -211,8 +185,8 @@ class StockTest extends \PHPUnit_Framework_TestCase $stockStatusMock->expects($this->once()) ->method('addIsInStockFilterToCollection') ->with($collectionMock); - $this->objectManagerMock->expects($this->once()) - ->method('get') + $this->statusFactoryMock->expects($this->once()) + ->method('create') ->willReturn($stockStatusMock); $this->assertNull($this->stock->addIsInStockFilterToCollection($collectionMock)); diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml index a57b542a0b12cf390a47524aaedd62f1100041bb..4332e525c3260619830f2e5f2102759edf2e346e 100644 --- a/app/code/Magento/CatalogInventory/etc/di.xml +++ b/app/code/Magento/CatalogInventory/etc/di.xml @@ -30,6 +30,8 @@ <preference for="Magento\CatalogInventory\Model\Spi\StockRegistryProviderInterface" type="Magento\CatalogInventory\Model\StockRegistryProvider" /> <preference for="Magento\CatalogInventory\Model\Spi\StockStateProviderInterface" type="Magento\CatalogInventory\Model\StockStateProvider" /> + + <preference for="Magento\CatalogInventory\Model\Resource\QtyCounterInterface" type="\Magento\CatalogInventory\Model\Resource\Stock" /> <type name="Magento\CatalogInventory\Observer\UpdateItemsStockUponConfigChangeObserver"> <arguments> diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php index 3f7477dbd79c7ee9ecb6c6f2153ca7e022ebc895..a4627d0c12c75ed3a1db9e542c739c2bc716803c 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php @@ -248,7 +248,7 @@ class IndexBuilder ] ); - if (!$rule->getConditions()->validate($product)) { + if (!$rule->validate($product)) { $this->connection->delete( $this->resource->getTableName('catalogrule_product_price'), [$this->connection->quoteInto('product_id = ?', $productId)] @@ -595,17 +595,6 @@ class IndexBuilder $select->where('rp.product_id=?', $productId); } - /** - * Join group price to result - */ - $groupPriceAttr = $this->eavConfig->getAttribute(Product::ENTITY, 'group_price'); - $select->joinLeft( - ['gp' => $groupPriceAttr->getBackend()->getResource()->getMainTable()], - 'gp.entity_id=rp.product_id AND gp.customer_group_id=rp.customer_group_id AND ' - . $this->connection->getCheckSql('gp.website_id=0', 'TRUE', 'gp.website_id=rp.website_id'), - 'value' - ); - /** * Join default price and websites prices to result */ @@ -647,10 +636,7 @@ class IndexBuilder [] ); $select->columns([ - 'default_price' => $this->connection->getIfNullSql( - 'gp.value', - $this->connection->getIfNullSql($tableAlias . '.value', 'pp_default.value') - ), + 'default_price' =>$this->connection->getIfNullSql($tableAlias . '.value', 'pp_default.value'), ]); return $this->connection->query($select); diff --git a/app/code/Magento/CatalogRule/Model/Rule.php b/app/code/Magento/CatalogRule/Model/Rule.php index 859dbb110dec4a5962ab2e7fa8a5c1914b53166a..ff05ab857f3be04a6769137d3a6d89cf184b26c3 100644 --- a/app/code/Magento/CatalogRule/Model/Rule.php +++ b/app/code/Magento/CatalogRule/Model/Rule.php @@ -347,6 +347,58 @@ class Rule extends \Magento\Rule\Model\AbstractModel return $map; } + /** + * {@inheritdoc} + */ + public function validateData(\Magento\Framework\DataObject $dataObject) + { + $result = parent::validateData($dataObject); + if ($result === true) { + $result = []; + } + + $action = $dataObject->getData('simple_action'); + $discount = $dataObject->getData('discount_amount'); + $result = array_merge($result, $this->validateDiscount($action, $discount)); + if ($dataObject->getData('sub_is_enable') == 1) { + $action = $dataObject->getData('sub_simple_action'); + $discount = $dataObject->getData('sub_discount_amount'); + $result = array_merge($result, $this->validateDiscount($action, $discount)); + } + + return !empty($result) ? $result : true; + } + + /** + * Validate discount based on action + * + * @param string $action + * @param string|int|float $discount + * + * @return array Validation errors + */ + protected function validateDiscount($action, $discount) + { + $result = []; + switch ($action) { + case 'by_percent': + case 'to_percent': + if ($discount < 0 || $discount > 100) { + $result[] = __('Percentage discount should be between 0 and 100.'); + }; + break; + case 'by_fixed': + case 'to_fixed': + if ($discount < 0) { + $result[] = __('Discount value should be 0 or greater.'); + }; + break; + default: + $result[] = __('Unknown action.'); + } + return $result; + } + /** * Calculate price using catalog price rule of product * diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php index 3687d55d4710fba30b24b144e14c0691e972730f..390543bf74ffcbbac61fa26431ec4af2b37b3655 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php @@ -168,7 +168,6 @@ class IndexBuilderTest extends \PHPUnit_Framework_TestCase $this->rules->expects($this->any())->method('getId')->will($this->returnValue(1)); $this->rules->expects($this->any())->method('getWebsiteIds')->will($this->returnValue([1])); - $this->rules->expects($this->any())->method('getConditions')->will($this->returnValue($this->combine)); $this->rules->expects($this->any())->method('getCustomerGroupIds')->will($this->returnValue([1])); $this->ruleCollectionFactory->expects($this->any())->method('create')->will($this->returnSelf()); @@ -180,7 +179,7 @@ class IndexBuilderTest extends \PHPUnit_Framework_TestCase $this->product->expects($this->any())->method('getId')->will($this->returnValue(1)); $this->product->expects($this->any())->method('getWebsiteIds')->will($this->returnValue([1])); - $this->combine->expects($this->any())->method('validate')->will($this->returnValue(true)); + $this->rules->expects($this->any())->method('validate')->with($this->product)->willReturn(true); $this->attribute->expects($this->any())->method('getBackend')->will($this->returnValue($this->backend)); $this->productFactory->expects($this->any())->method('create')->will($this->returnValue($this->product)); @@ -205,7 +204,7 @@ class IndexBuilderTest extends \PHPUnit_Framework_TestCase */ public function testUpdateCatalogRuleGroupWebsiteData() { - $groupPriceAttrMock = $this->getMock( + $priceAttrMock = $this->getMock( 'Magento\Catalog\Model\Entity\Attribute', ['getBackend'], [], @@ -213,39 +212,35 @@ class IndexBuilderTest extends \PHPUnit_Framework_TestCase false ); $backendModelMock = $this->getMock( - 'Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice', + 'Magento\Catalog\Model\Product\Attribute\Backend\Tierprice', ['getResource'], [], '', false ); $resourceMock = $this->getMock( - 'Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice', + 'Magento\Catalog\Model\Resource\Product\Attribute\Backend\Tierprice', ['getMainTable'], [], '', false ); - $resourceMock->expects($this->once()) + $resourceMock->expects($this->any()) ->method('getMainTable') - ->will($this->returnValue('catalog_product_entity_group_price')); - $backendModelMock->expects($this->once()) + ->will($this->returnValue('catalog_product_entity_tear_price')); + $backendModelMock->expects($this->any()) ->method('getResource') ->will($this->returnValue($resourceMock)); - $groupPriceAttrMock->expects($this->once()) + $priceAttrMock->expects($this->any()) ->method('getBackend') ->will($this->returnValue($backendModelMock)); $this->eavConfig->expects($this->at(0)) - ->method('getAttribute') - ->with(\Magento\Catalog\Model\Product::ENTITY, 'group_price') - ->will($this->returnValue($groupPriceAttrMock)); - $this->eavConfig->expects($this->at(1)) ->method('getAttribute') ->with(\Magento\Catalog\Model\Product::ENTITY, 'price') ->will($this->returnValue($this->attribute)); $this->select->expects($this->once())->method('insertFromSelect')->with('catalogrule_group_website'); - + $this->indexBuilder->reindexByIds([1]); } } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php index dbf6958530ac87af19504c393fc8fae0765b7d08..80f4ef5eed5ece794faef9fdc4b19f22ca4bb9af 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php @@ -8,6 +8,11 @@ namespace Magento\CatalogRule\Test\Unit\Model; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +/** + * Class RuleTest + * @package Magento\CatalogRule\Test\Unit\Model + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class RuleTest extends \PHPUnit_Framework_TestCase { /** @var \Magento\CatalogRule\Model\Rule */ @@ -200,6 +205,110 @@ class RuleTest extends \PHPUnit_Framework_TestCase ]; } + /** + * Test validateData action + * + * @dataProvider validateDataDataProvider + * @param array $data Data for the rule actions + * @param bool|array $expected True or an array of errors + * + * @return void + */ + public function testValidateData($data, $expected) + { + $result = $this->rule->validateData(new \Magento\Framework\DataObject($data)); + $this->assertEquals($result, $expected); + } + + /** + * Data provider for testValidateData test + * + * @return array + */ + public function validateDataDataProvider() + { + return [ + [ + [ + 'simple_action' => 'by_fixed', + 'discount_amount' => '123', + 'sub_is_enable' => '0', + 'sub_simple_action' => 'by_percent', + 'sub_discount_amount' => '123', + ], + true + ], + [ + [ + 'simple_action' => 'by_percent', + 'discount_amount' => '9,99', + 'sub_is_enable' => '0', + ], + true + ], + [ + [ + 'simple_action' => 'by_fixed', + 'discount_amount' => '123', + 'sub_is_enable' => '1', + 'sub_simple_action' => 'by_percent', + 'sub_discount_amount' => '123', + ], + [ + 'Percentage discount should be between 0 and 100.', + ] + ], + [ + [ + 'simple_action' => 'by_percent', + 'discount_amount' => '123.12', + 'sub_is_enable' => '1', + 'sub_simple_action' => 'to_percent', + 'sub_discount_amount' => '123.001', + ], + [ + 'Percentage discount should be between 0 and 100.', + 'Percentage discount should be between 0 and 100.', + ] + ], + [ + [ + 'simple_action' => 'to_percent', + 'discount_amount' => '-12', + 'sub_is_enable' => '1', + 'sub_simple_action' => 'to_fixed', + 'sub_discount_amount' => '567.8901', + ], + [ + 'Percentage discount should be between 0 and 100.', + ] + ], + [ + [ + 'simple_action' => 'to_fixed', + 'discount_amount' => '-1234567890', + 'sub_is_enable' => '1', + 'sub_simple_action' => 'by_fixed', + 'sub_discount_amount' => '-5', + ], + [ + 'Discount value should be 0 or greater.', + 'Discount value should be 0 or greater.', + ] + ], + [ + [ + 'simple_action' => 'invalid action', + 'discount_amount' => '12', + 'sub_is_enable' => '0', + ], + [ + 'Unknown action.', + ] + ], + ]; + } + /** * Test after delete action * @@ -228,9 +337,10 @@ class RuleTest extends \PHPUnit_Framework_TestCase } /** - * Test IsRuleBehaviorChanged action + * Test isRuleBehaviorChanged action + * + * @dataProvider isRuleBehaviorChangedDataProvider * - * @dataProvider ruleData * @param array $dataArray * @param array $originDataArray * @param bool $isObjectNew @@ -258,11 +368,11 @@ class RuleTest extends \PHPUnit_Framework_TestCase } /** - * Data provider for isRuleBehaviorChanged test + * Data provider for testIsRuleBehaviorChanged test * * @return array */ - public function ruleData() + public function isRuleBehaviorChangedDataProvider() { return [ [['new name', 'new description'], ['name', 'description'], false, false], diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index 8abce05a972830229256bd5475aad5b4452b38ad..887ed8b40e83cf9513fb518be418a16b42bc284e 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -9,10 +9,12 @@ "magento/module-customer": "1.0.0-beta", "magento/module-backend": "1.0.0-beta", "magento/module-eav": "1.0.0-beta", - "magento/module-import-export": "1.0.0-beta", "magento/framework": "1.0.0-beta", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-import-export": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/CatalogRule/etc/mview.xml b/app/code/Magento/CatalogRule/etc/mview.xml index 4fbb8a21147cc47f2809aba0a691d7b82a75ef84..9959fbb75d98aa0b30a6d808885cf83786fff24b 100644 --- a/app/code/Magento/CatalogRule/etc/mview.xml +++ b/app/code/Magento/CatalogRule/etc/mview.xml @@ -17,9 +17,8 @@ <table name="catalog_product_entity_datetime" entity_column="entity_id" /> <table name="catalog_product_entity_decimal" entity_column="entity_id" /> <table name="catalog_product_entity_gallery" entity_column="entity_id" /> - <table name="catalog_product_entity_group_price" entity_column="entity_id" /> <table name="catalog_product_entity_int" entity_column="entity_id" /> - <table name="catalog_product_entity_media_gallery" entity_column="entity_id" /> + <table name="catalog_product_entity_media_gallery" entity_column="value_id" /> <table name="catalog_product_entity_media_gallery_value" entity_column="entity_id" /> <table name="catalog_product_entity_text" entity_column="entity_id" /> <table name="catalog_product_entity_tier_price" entity_column="entity_id" /> diff --git a/app/code/Magento/CatalogRule/i18n/de_DE.csv b/app/code/Magento/CatalogRule/i18n/de_DE.csv index e5ac9c2fcdef8bfac205f90ab801001bc731ec19..2cfceb25ae29b3c8535b66e5a14384de4ee0a623 100644 --- a/app/code/Magento/CatalogRule/i18n/de_DE.csv +++ b/app/code/Magento/CatalogRule/i18n/de_DE.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination",Bedingungenkombination "Product Attribute",Produktattribut "The rules have been applied.","Die Regeln wurden angewendet." +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/i18n/en_US.csv b/app/code/Magento/CatalogRule/i18n/en_US.csv index aab29051ead73af95b6155cd2734ec5621db5de7..2a931e8b33161f90787eaca154c140360a6aeac1 100644 --- a/app/code/Magento/CatalogRule/i18n/en_US.csv +++ b/app/code/Magento/CatalogRule/i18n/en_US.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination","Conditions Combination" "Product Attribute","Product Attribute" "The rules have been applied.","The rules have been applied." +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/i18n/es_ES.csv b/app/code/Magento/CatalogRule/i18n/es_ES.csv index f49ec6eddb9d3f9645b141d375657d399f18ab7e..acd65c67ccdb2b5e26b074cc87412e51835fed7f 100644 --- a/app/code/Magento/CatalogRule/i18n/es_ES.csv +++ b/app/code/Magento/CatalogRule/i18n/es_ES.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination","Combinación de condiciones" "Product Attribute","Atributos del producto" "The rules have been applied.","Las reglas han sido aplicadas." +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/i18n/fr_FR.csv b/app/code/Magento/CatalogRule/i18n/fr_FR.csv index f003629331ba8db57eff2ab85cb70a5a8c1f8b3e..5349ab2025341e751ea891d5601a411d55d94c40 100644 --- a/app/code/Magento/CatalogRule/i18n/fr_FR.csv +++ b/app/code/Magento/CatalogRule/i18n/fr_FR.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination","Combinaison de conditions" "Product Attribute","Attribut de produit" "The rules have been applied.","Les règles ont été appliquées." +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/i18n/nl_NL.csv b/app/code/Magento/CatalogRule/i18n/nl_NL.csv index 5c21f95f6eabba150428a7c51edb4102bb6ad582..6bc287b3c44cea7d6d72af7aaf9882652d415197 100644 --- a/app/code/Magento/CatalogRule/i18n/nl_NL.csv +++ b/app/code/Magento/CatalogRule/i18n/nl_NL.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination",Voorwaardencombinaties "Product Attribute","Product attribuut" "The rules have been applied.","De regels zijn ingesteld." +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/i18n/pt_BR.csv b/app/code/Magento/CatalogRule/i18n/pt_BR.csv index 39db9b3f737aa1b8037bc0967117f1c7ced1ef3f..77c2c40996739516e7fd8849a8e246462bd59b1a 100644 --- a/app/code/Magento/CatalogRule/i18n/pt_BR.csv +++ b/app/code/Magento/CatalogRule/i18n/pt_BR.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination","Combinação de condições" "Product Attribute","Atributo de Produto" "The rules have been applied.","As regras foram aplicadas." +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/i18n/zh_CN.csv b/app/code/Magento/CatalogRule/i18n/zh_CN.csv index 4d291b895c161b9428069c891c095eea0d5933bb..e1fe27f48fbf548174ad16348e1f547747c26cf8 100644 --- a/app/code/Magento/CatalogRule/i18n/zh_CN.csv +++ b/app/code/Magento/CatalogRule/i18n/zh_CN.csv @@ -68,3 +68,6 @@ Promo,Promo "Conditions Combination",æ¡ä»¶ç»„åˆ "Product Attribute",产å“属性 "The rules have been applied.",规则已应用。 +"Percentage discount should be between 0 and 100.","Percentage discount should be between 0 and 100." +"Discount value should be 0 or greater.","Discount value should be 0 or greater." +"Unknown action.","Unknown action." diff --git a/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/js.phtml b/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/js.phtml index 8e87adac441dbf88047073fbf9044fbe4c2ecdd1..a3604faa6d38ccf46a41fa16145bc1a8b5fea6c3 100644 --- a/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/js.phtml +++ b/app/code/Magento/CatalogRule/view/adminhtml/templates/promo/js.phtml @@ -13,15 +13,53 @@ function hideShowSubproductOptions() $('rule_sub_simple_action').up('div.field').show(); $('rule_sub_discount_amount').up('div.field').show(); $('rule_sub_discount_amount').addClassName('required-entry validate-not-negative-number'); + changeValidationForSubDiscountPercent(); } else { + $('rule_sub_discount_amount'). + removeClassName('required-entry'). + removeClassName('validate-not-negative-number'). + removeClassName('validate-number-range'). + removeClassName('number-range-0.00-100.00'); $('rule_sub_simple_action').up('div.field').hide(); $('rule_sub_discount_amount').up('div.field').hide(); - $('rule_sub_discount_amount').removeClassName('required-entry').removeClassName('validate-not-negative-number'); } return true; } + +function changeValidationForDiscountPercent() +{ + if ($('rule_simple_action').value == 'by_percent' || $('rule_simple_action').value == 'to_percent') { + $('rule_discount_amount').addClassName('validate-number-range number-range-0.00-100.00'); + } else { + $('rule_discount_amount').removeClassName('validate-number-range').removeClassName('number-range-0.00-100.00'); + } + + return true; +} + +function changeValidationForSubDiscountPercent() +{ + if ($('rule_sub_is_enable').value == 1) { + if ($('rule_sub_simple_action').value == 'by_percent' || $('rule_sub_simple_action').value == 'to_percent') { + $('rule_sub_discount_amount').addClassName('validate-number-range number-range-0.00-100.00'); + } else { + $('rule_sub_discount_amount'). + removeClassName('validate-number-range'). + removeClassName('number-range-0.00-100.00'); + } + } + + return true; +} + jQuery(document).ready(hideShowSubproductOptions); +jQuery(document).ready(changeValidationForDiscountPercent); +jQuery(document).on('change', '#rule_sub_is_enable', hideShowSubproductOptions); +jQuery(document).on('change', '#rule_simple_action', changeValidationForDiscountPercent); +jQuery(document).on('change', '#rule_sub_simple_action', changeValidationForSubDiscountPercent); window.hideShowSubproductOptions = hideShowSubproductOptions; +window.changeValidationForDiscountPercent = changeValidationForDiscountPercent; +window.changeValidationForSubDiscountPercent = changeValidationForSubDiscountPercent; }); </script> diff --git a/app/code/Magento/CatalogRuleConfigurable/LICENSE.txt b/app/code/Magento/CatalogRuleConfigurable/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/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/CatalogRuleConfigurable/LICENSE_AFL.txt b/app/code/Magento/CatalogRuleConfigurable/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..87943b95d43a5bb8736093275afe3cb8e1d93d26 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/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. \ No newline at end of file diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..c9a049eac678545bc27a8265aa6693e32b2f8966 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php @@ -0,0 +1,37 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model; + +class ConfigurableProductsProvider +{ + /** @var \Magento\Framework\App\Resource */ + private $resource; + + /** + * @param \Magento\Framework\App\Resource $resource + */ + public function __construct(\Magento\Framework\App\Resource $resource) + { + $this->resource = $resource; + } + + /** + * @param array $ids + * @return array + */ + public function getIds(array $ids) + { + $connection = $this->resource->getConnection(); + return $connection->fetchCol( + $connection + ->select() + ->from(['e' => $this->resource->getTableName('catalog_product_entity')], ['e.entity_id']) + ->where('e.type_id = ?', \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE) + ->where('e.entity_id IN (?)', $ids) + ); + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Indexer/ProductRuleReindex.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Indexer/ProductRuleReindex.php new file mode 100644 index 0000000000000000000000000000000000000000..642736cff51c735a74201e91b5fbbc0d8245040a --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Indexer/ProductRuleReindex.php @@ -0,0 +1,93 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Indexer; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\ConfigurableProductsProvider; + +/** + * Class ReindexProduct. Add configurable sub-products to reindex + */ +class ProductRuleReindex +{ + /** @var Configurable */ + private $configurable; + + /** @var ConfigurableProductsProvider */ + private $configurableProductsProvider; + + /** + * @param Configurable $configurable + * @param ConfigurableProductsProvider $configurableProductsProvider + */ + public function __construct( + Configurable $configurable, + ConfigurableProductsProvider $configurableProductsProvider + ) { + $this->configurable = $configurable; + $this->configurableProductsProvider = $configurableProductsProvider; + } + + /** + * @param \Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer $subject + * @param \Closure $proceed + * @param int $id + * + * @return void + */ + public function aroundExecuteRow( + \Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer $subject, + \Closure $proceed, + $id + ) { + $configurableProductIds = $this->configurableProductsProvider->getIds([$id]); + $this->reindexSubProducts($configurableProductIds, $subject); + if (!$configurableProductIds) { + $proceed($id); + } + } + + /** + * @param \Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer $subject + * @param \Closure $proceed + * @param array $ids + * + * @return void + */ + public function aroundExecuteList( + \Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer $subject, + \Closure $proceed, + array $ids + ) { + $configurableProductIds = $this->configurableProductsProvider->getIds($ids); + $subProducts = $this->reindexSubProducts($configurableProductIds, $subject); + $ids = array_diff($ids, $configurableProductIds, $subProducts); + if ($ids) { + $proceed($ids); + } + } + + /** + * @param array $configurableIds + * @param \Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer $subject + * + * @return array + */ + private function reindexSubProducts( + array $configurableIds, + \Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer $subject + ) { + $subProducts = []; + if ($configurableIds) { + $subProducts = array_values($this->configurable->getChildrenIds($configurableIds)[0]); + if ($subProducts) { + $subject->executeList($subProducts); + } + } + return $subProducts; + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..156ec7edd7ef743117ce6b61bd6259e92473b51c --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandler.php @@ -0,0 +1,83 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\ConfigurableProductsProvider; + +/** + * Add configurable sub products to catalog rule indexer on full reindex + */ +class ConfigurableProductHandler +{ + /** @var \Magento\ConfigurableProduct\Model\Resource\Product\Type\Configurable */ + private $configurable; + + /** @var ConfigurableProductsProvider */ + private $configurableProductsProvider; + + /** @var array */ + private $subProductsValidationResults = []; + + /** + * @param \Magento\ConfigurableProduct\Model\Resource\Product\Type\Configurable $configurable + * @param ConfigurableProductsProvider $configurableProductsProvider + */ + public function __construct( + \Magento\ConfigurableProduct\Model\Resource\Product\Type\Configurable $configurable, + ConfigurableProductsProvider $configurableProductsProvider + ) { + $this->configurable = $configurable; + $this->configurableProductsProvider = $configurableProductsProvider; + } + + /** + * @param \Magento\CatalogRule\Model\Rule $rule + * @param array $productIds + * @return array + */ + public function afterGetMatchingProductIds(\Magento\CatalogRule\Model\Rule $rule, array $productIds) + { + $configurableProductIds = $this->configurableProductsProvider->getIds(array_keys($productIds)); + foreach ($configurableProductIds as $productId) { + $subProductsIds = $this->configurable->getChildrenIds($productId)[0]; + $parentValidationResult = $productIds[$productId]; + foreach ($subProductsIds as $subProductsId) { + $productIds[$subProductsId] = $this->getSubProductValidationResult( + $rule->getId(), + $subProductsId, + $parentValidationResult + ); + } + unset($productIds[$productId]); + } + return $productIds; + } + + /** + * Return validation result for sub-product. + * If any of configurable product is valid for current rule, then their sub-product must be valid too + * + * @param int $urlId + * @param int $subProductsId + * @param array $parentValidationResult + * @return array + */ + private function getSubProductValidationResult($urlId, $subProductsId, $parentValidationResult) + { + if (!isset($this->subProductsValidationResults[$urlId][$subProductsId])) { + $this->subProductsValidationResults[$urlId][$subProductsId] = array_filter($parentValidationResult); + } else { + $parentValidationResult = array_intersect_key( + $this->subProductsValidationResults[$urlId][$subProductsId] + $parentValidationResult, + $parentValidationResult + ); + $this->subProductsValidationResults[$urlId][$subProductsId] = $parentValidationResult; + } + return $parentValidationResult; + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/Validation.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/Validation.php new file mode 100644 index 0000000000000000000000000000000000000000..0419af2307d710f0a42c1fa13498be23cca148b9 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/Rule/Validation.php @@ -0,0 +1,50 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule; + +use \Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Class Validation. Call validate method for configurable product instead simple product + */ +class Validation +{ + /** @var Configurable */ + private $configurable; + + /** + * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurableType + */ + public function __construct(Configurable $configurableType) + { + $this->configurable = $configurableType; + } + + /** + * @param \Magento\CatalogRule\Model\Rule $rule + * @param \Closure $proceed + * @param \Magento\Framework\DataObject|\Magento\Catalog\Model\Product $product + * @return bool + */ + public function aroundValidate( + \Magento\CatalogRule\Model\Rule $rule, + \Closure $proceed, + \Magento\Framework\DataObject $product + ) { + $validateResult = $proceed($product); + if (!$validateResult && ($configurableProducts = $this->configurable->getParentIdsByChild($product->getId()))) { + foreach ($configurableProducts as $configurableProductId) { + $validateResult = $rule->getConditions()->validateByEntityId($configurableProductId); + // If any of configurable product is valid for current rule, then their sub-product must be valid too + if ($validateResult) { + break; + } + } + } + return $validateResult; + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/README.md b/app/code/Magento/CatalogRuleConfigurable/README.md new file mode 100644 index 0000000000000000000000000000000000000000..35c3244db5a134d5f7efe13c45698fdbf77ea0e9 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/README.md @@ -0,0 +1 @@ +Magento_CatalogRuleConfigurable module is an extension of Magento_CatalogRule and Magento_ConfigurableProduct modules that handle catalog rule indexer for configurable product diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php b/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..54ced70d8e033151a87f5afed9de8a7eea947652 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ConfigurableProductHandlerTest.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogRuleConfigurable\Test\Unit\Plugin\CatalogRule\Model\Rule; + +use Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\ConfigurableProductHandler; +use Magento\ConfigurableProduct\Model\Resource\Product\Type\Configurable; +use Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\ConfigurableProductsProvider; + +/** + * Unit test for Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\ConfigurableProductHandler + */ +class ConfigurableProductHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\ConfigurableProductHandler + */ + private $configurableProductHandler; + + /** + * @var Configurable|\PHPUnit_Framework_MockObject_MockObject + */ + private $configurableMock; + + /** + * @var ConfigurableProductsProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $configurableProductsProviderMock; + + /** @var \Magento\CatalogRule\Model\Rule||\PHPUnit_Framework_MockObject_MockObject */ + private $ruleMock; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->configurableMock = $this->getMock( + 'Magento\ConfigurableProduct\Model\Resource\Product\Type\Configurable', + ['getChildrenIds'], + [], + '', + false + ); + $this->configurableProductsProviderMock = $this->getMock( + 'Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\ConfigurableProductsProvider', + ['getIds'], + [], + '', + false + ); + $this->ruleMock = $this->getMock('Magento\CatalogRule\Model\Rule', [], [], '', false); + + $this->configurableProductHandler = new ConfigurableProductHandler( + $this->configurableMock, + $this->configurableProductsProviderMock + ); + } + + /** + * @return void + */ + public function testAfterGetMatchingProductIdsWithSimpleProduct() + { + $this->configurableProductsProviderMock->expects($this->once())->method('getIds')->willReturn([]); + $this->configurableMock->expects($this->never())->method('getChildrenIds'); + + $productIds = ['product' => 'valid results']; + $this->assertEquals( + $productIds, + $this->configurableProductHandler->afterGetMatchingProductIds($this->ruleMock, $productIds) + ); + } + + /** + * @return void + */ + public function testAfterGetMatchingProductIdsWithConfigurableProduct() + { + $this->configurableProductsProviderMock->expects($this->once())->method('getIds') + ->willReturn(['conf1', 'conf2']); + $this->configurableMock->expects($this->any())->method('getChildrenIds')->willReturnMap([ + ['conf1', true, [ 0 => ['simple1']]], + ['conf2', true, [ 0 => ['simple1', 'simple2']]], + ]); + + $this->assertEquals( + [ + 'simple1' => [ + 0 => true, + 1 => true, + 3 => true, + 4 => false, + ], + 'simple2' => [ + 0 => false, + 1 => false, + 3 => true, + 4 => false, + ] + ], + $this->configurableProductHandler->afterGetMatchingProductIds( + $this->ruleMock, + [ + 'conf1' => [ + 0 => true, + 1 => true, + ], + 'conf2' => [ + 0 => false, + 1 => false, + 3 => true, + 4 => false, + ], + ] + ) + ); + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ValidationTest.php b/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ValidationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a91a4b88f7fd4579516b19db4c871f4472294e84 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Unit/Plugin/CatalogRule/Model/Rule/ValidationTest.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogRuleConfigurable\Test\Unit\Plugin\CatalogRule\Model\Rule; + +use Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\Validation; + +/** + * Unit test for Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\Validation + */ +class ValidationTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\Validation + */ + private $validation; + + /** + * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable|\PHPUnit_Framework_MockObject_MockObject + */ + private $configurableMock; + + /** @var \Magento\CatalogRule\Model\Rule|\PHPUnit_Framework_MockObject_MockObject */ + private $ruleMock; + + /** @var \Magento\Rule\Model\Condition\Combine|\PHPUnit_Framework_MockObject_MockObject */ + private $ruleConditionsMock; + + /** @var \Magento\Framework\DataObject|\PHPUnit_Framework_MockObject_MockObject */ + private $productMock; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->configurableMock = $this->getMock( + 'Magento\ConfigurableProduct\Model\Product\Type\Configurable', + ['getParentIdsByChild'], + [], + '', + false + ); + + + $this->ruleMock = $this->getMock('Magento\CatalogRule\Model\Rule', [], [], '', false); + $this->ruleConditionsMock = $this->getMock('Magento\Rule\Model\Condition\Combine', [], [], '', false); + $this->productMock = $this->getMock('Magento\Framework\DataObject', ['getId']); + + $this->validation = new Validation( + $this->configurableMock + ); + } + + /** + * @param $parentsIds + * @param $validationResult + * @param $runValidateAmount + * @param $result + * @dataProvider dataProviderForValidateWithValidConfigurableProduct + * @return void + */ + public function testAroundValidateWithValidConfigurableProduct( + $parentsIds, + $validationResult, + $runValidateAmount, + $result + ) { + $closureMock = function () { + return false; + }; + + $this->productMock->expects($this->once())->method('getId')->willReturn('product_id'); + $this->configurableMock->expects($this->once())->method('getParentIdsByChild')->with('product_id') + ->willReturn($parentsIds); + $this->ruleMock->expects($this->exactly($runValidateAmount))->method('getConditions') + ->willReturn($this->ruleConditionsMock); + $this->ruleConditionsMock->expects($this->exactly($runValidateAmount))->method('validateByEntityId') + ->willReturnMap($validationResult); + + $this->assertEquals( + $result, + $this->validation->aroundValidate($this->ruleMock, $closureMock, $this->productMock) + ); + } + + /** + * @return array + */ + public function dataProviderForValidateWithValidConfigurableProduct() + { + return [ + [ + [1, 2, 3], + [ + [1, false], + [2, true], + [3, true], + ], + 2, + true, + ], + [ + [1, 2, 3], + [ + [1, true], + [2, false], + [3, true], + ], + 1, + true, + ], + [ + [1, 2, 3], + [ + [1, false], + [2, false], + [3, false], + ], + 3, + false, + ], + ]; + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..fc6bea322d3c59aacb561c8b166823a5d3ea0720 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-catalog-rule-configurable", + "description": "N/A", + "require": { + "php": "~5.5.0|~5.6.0|~7.0.0", + "magento/module-configurable-product": "1.0.0-beta", + "magento/framework": "1.0.0-beta", + "magento/magento-composer-installer": "*" + }, + "suggest": { + "magento/module-catalog-rule": "1.0.0-beta" + }, + "type": "magento2-module", + "version": "1.0.0-beta", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "extra": { + "map": [ + [ + "*", + "Magento/CatalogRuleConfigurable" + ] + ] + } +} diff --git a/app/code/Magento/CatalogRuleConfigurable/etc/adminhtml/di.xml b/app/code/Magento/CatalogRuleConfigurable/etc/adminhtml/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..051dd103f5d62c20ad7315ab075ed3ee7b7cd22c --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/etc/adminhtml/di.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <type name="Magento\CatalogRule\Model\Rule"> + <plugin name="addVariationsToProductRule" type="Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\ConfigurableProductHandler"/> + <plugin name="configurableChildValidation" type="Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Rule\Validation"/> + </type> + <type name="Magento\CatalogRule\Model\Indexer\Product\ProductRuleIndexer"> + <plugin name="productRuleReindex" type="Magento\CatalogRuleConfigurable\Plugin\CatalogRule\Model\Indexer\ProductRuleReindex"/> + </type> +</config> diff --git a/app/code/Magento/CatalogRuleConfigurable/etc/module.xml b/app/code/Magento/CatalogRuleConfigurable/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..ed99399ed8cc75369164307727b3e4be7b303625 --- /dev/null +++ b/app/code/Magento/CatalogRuleConfigurable/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> + <module name="Magento_CatalogRuleConfigurable" setup_version="2.0.0"> + <sequence> + <module name="Magento_CatalogRule"/> + <module name="Magento_ConfigurableProduct"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php index 037e4c3629e034b6ef57564abbcf8daa025f8644..8f8fb11c19d8513f40427af224ee2fe6921cb783 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php @@ -97,29 +97,26 @@ class Preprocessor implements PreprocessorInterface */ private function processQueryWithField(FilterInterface $filter, $isNegation, $query) { - $currentStoreId = $this->scopeResolver->getScope()->getId(); - $select = null; /** @var \Magento\Catalog\Model\Resource\Eav\Attribute $attribute */ $attribute = $this->config->getAttribute(Product::ENTITY, $filter->getField()); - $table = $attribute->getBackendTable(); if ($filter->getField() === 'price') { - $filterQuery = str_replace( + $resultQuery = str_replace( $this->connection->quoteIdentifier('price'), $this->connection->quoteIdentifier('price_index.min_price'), $query ); - return $filterQuery; } elseif ($filter->getField() === 'category_ids') { return 'category_ids_index.category_id = ' . $filter->getValue(); } elseif ($attribute->isStatic()) { $alias = $this->tableMapper->getMappingAlias($filter); - $filterQuery = str_replace( + $resultQuery = str_replace( $this->connection->quoteIdentifier($attribute->getAttributeCode()), $this->connection->quoteIdentifier($alias . '.' . $attribute->getAttributeCode()), $query ); - return $filterQuery; - } elseif ($filter->getType() === FilterInterface::TYPE_TERM) { + } elseif ($filter->getType() === FilterInterface::TYPE_TERM + && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true) + ) { $alias = $this->tableMapper->getMappingAlias($filter); if (is_array($filter->getValue())) { $value = sprintf( @@ -130,16 +127,18 @@ class Preprocessor implements PreprocessorInterface } else { $value = ($isNegation ? '!' : '') . '= ' . $filter->getValue(); } - $filterQuery = sprintf( + $resultQuery = sprintf( '%1$s.value %2$s', $alias, $value ); - return $filterQuery; } else { + $table = $attribute->getBackendTable(); $select = $this->connection->select(); $ifNullCondition = $this->connection->getIfNullSql('current_store.value', 'main_table.value'); + $currentStoreId = $this->scopeResolver->getScope()->getId(); + $select->from(['main_table' => $table], 'entity_id') ->joinLeft( ['current_store' => $table], @@ -154,10 +153,12 @@ class Preprocessor implements PreprocessorInterface ) ->where('main_table.store_id = ?', Store::DEFAULT_STORE_ID) ->having($query); - } - return 'search_index.entity_id IN ( - select entity_id from ' . $this->conditionManager->wrapBrackets($select) . ' as filter + $resultQuery = 'search_index.entity_id IN ( + select entity_id from ' . $this->conditionManager->wrapBrackets($select) . ' as filter )'; + } + + return $resultQuery; } } diff --git a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php index c951e9b538f12e22367e682ce335742c3c2cccdf..5dd7633026068b4dea7253e9d5d36e1611a265c0 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php +++ b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php @@ -103,9 +103,10 @@ class TableMapper if ($fieldToTableMap) { list($alias, $table, $mapOn, $mappedFields) = $fieldToTableMap; $table = $this->resource->getTableName($table); - } elseif ($filter->getType() === FilterInterface::TYPE_TERM) { - $attribute = $this->getAttributeByCode($field); - if ($attribute) { + } elseif ($attribute = $this->getAttributeByCode($field)) { + if ($filter->getType() === FilterInterface::TYPE_TERM + && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true) + ) { $table = $this->resource->getTableName('catalog_product_index_eav'); $alias = $field . '_filter'; $mapOn = sprintf( @@ -115,21 +116,14 @@ class TableMapper $this->getStoreId() ); $mappedFields = []; - } - } else { - $attribute = $this->getAttributeByCode($field); - if ($attribute && $attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) { + } elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) { $table = $attribute->getBackendTable(); - $alias = $this->getTableAlias($table); + $alias = $field . '_filter'; $mapOn = 'search_index.entity_id = ' . $alias . '.entity_id'; $mappedFields = null; } } - if (!$alias && $table) { - $alias = $this->getTableAlias($table); - } - return [$alias, $table, $mapOn, $mappedFields]; } @@ -242,15 +236,6 @@ class TableMapper return array_key_exists($field, $fieldToTableMap) ? $fieldToTableMap[$field] : null; } - /** - * @param string $table - * @return string - */ - private function getTableAlias($table) - { - return md5($table); - } - /** * @param string $field * @return \Magento\Catalog\Model\Resource\Eav\Attribute diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php index 2e5241e8e91f028334a05d333f623bc7981b473f..1208bf32c77a01fe1c692b7142660570be305b38 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php @@ -7,6 +7,7 @@ namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Filter; use Magento\Framework\DB\Select; +use Magento\Framework\Search\Request\FilterInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -46,7 +47,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase private $select; /** - * @var \Magento\Framework\Search\Request\FilterInterface|MockObject + * @var FilterInterface|MockObject */ private $filter; @@ -86,7 +87,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['getId']) ->getMockForAbstractClass(); - $this->scopeResolver->expects($this->once()) + $this->scopeResolver->expects($this->any()) ->method('getScope') ->will($this->returnValue($this->scope)); $this->config = $this->getMockBuilder('\Magento\Eav\Model\Config') @@ -95,7 +96,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase ->getMock(); $this->attribute = $this->getMockBuilder('\Magento\Eav\Model\Entity\Attribute\AbstractAttribute') ->disableOriginalConstructor() - ->setMethods(['getBackendTable', 'isStatic', 'getAttributeId', 'getAttributeCode']) + ->setMethods(['getBackendTable', 'isStatic', 'getAttributeId', 'getAttributeCode', 'getFrontendInput']) ->getMockForAbstractClass(); $this->resource = $resource = $this->getMockBuilder('\Magento\Framework\App\Resource') ->disableOriginalConstructor() @@ -120,7 +121,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($this->connection)); $this->filter = $this->getMockBuilder('\Magento\Framework\Search\Request\FilterInterface') ->disableOriginalConstructor() - ->setMethods(['getField', 'getValue']) + ->setMethods(['getField', 'getValue', 'getType']) ->getMockForAbstractClass(); $this->conditionManager->expects($this->any()) @@ -154,11 +155,9 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase public function testProcessPrice() { $expectedResult = 'price_index.min_price = 23'; - $scopeId = 0; $isNegation = false; $query = 'price = 23'; - $this->scope->expects($this->once())->method('getId')->will($this->returnValue($scopeId)); $this->filter->expects($this->exactly(2)) ->method('getField') ->will($this->returnValue('price')); @@ -174,11 +173,9 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase public function testProcessCategoryIds() { $expectedResult = 'category_ids_index.category_id = FilterValue'; - $scopeId = 0; $isNegation = false; $query = 'SELECT category_ids FROM catalog_product_entity'; - $this->scope->expects($this->once())->method('getId')->will($this->returnValue($scopeId)); $this->filter->expects($this->exactly(3)) ->method('getField') ->will($this->returnValue('category_ids')); @@ -199,7 +196,6 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase public function testProcessStaticAttribute() { $expectedResult = 'attr_table_alias.static_attribute LIKE %name%'; - $scopeId = 0; $isNegation = false; $query = 'static_attribute LIKE %name%'; @@ -207,7 +203,6 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase ->willReturn('static_attribute'); $this->tableMapper->expects($this->once())->method('getMappingAlias') ->willReturn('attr_table_alias'); - $this->scope->expects($this->once())->method('getId')->will($this->returnValue($scopeId)); $this->filter->expects($this->exactly(3)) ->method('getField') ->will($this->returnValue('static_attribute')); @@ -218,14 +213,100 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase $this->attribute->expects($this->once()) ->method('isStatic') ->will($this->returnValue(true)); - $this->attribute->expects($this->once()) - ->method('getBackendTable') - ->will($this->returnValue('backend_table')); $actualResult = $this->target->process($this->filter, $isNegation, $query); $this->assertSame($expectedResult, $this->removeWhitespaces($actualResult)); } + /** + * @dataProvider testTermFilterDataProvider + */ + public function testProcessTermFilter($frontendInput, $fieldValue, $isNegation, $expected) + { + $this->config->expects($this->exactly(1)) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'termField') + ->will($this->returnValue($this->attribute)); + + $this->attribute->expects($this->once()) + ->method('isStatic') + ->will($this->returnValue(false)); + + $this->filter->expects($this->once()) + ->method('getType') + ->willReturn(FilterInterface::TYPE_TERM); + $this->attribute->expects($this->once()) + ->method('getFrontendInput') + ->willReturn($frontendInput); + + $this->tableMapper->expects($this->once())->method('getMappingAlias') + ->willReturn('termAttrAlias'); + + $this->filter->expects($this->exactly(3)) + ->method('getField') + ->willReturn('termField'); + $this->filter->expects($this->exactly(2)) + ->method('getValue') + ->willReturn($fieldValue); + + $actualResult = $this->target->process($this->filter, $isNegation, 'This filter is not depends on used query'); + $this->assertSame($expected, $this->removeWhitespaces($actualResult)); + } + + public function testTermFilterDataProvider() + { + return [ + 'selectPositiveEqual' => [ + 'frontendInput' => 'select', + 'fieldValue' => 'positiveValue', + 'isNegation' => false, + 'expected' => 'termAttrAlias.value = positiveValue', + ], + 'selectPositiveArray' => [ + 'frontendInput' => 'select', + 'fieldValue' => [2, 3, 15], + 'isNegation' => false, + 'expected' => 'termAttrAlias.value IN (2,3,15)', + ], + 'selectNegativeEqual' => [ + 'frontendInput' => 'select', + 'fieldValue' => 'positiveValue', + 'isNegation' => true, + 'expected' => 'termAttrAlias.value != positiveValue', + ], + 'selectNegativeArray' => [ + 'frontendInput' => 'select', + 'fieldValue' => [4, 3, 42], + 'isNegation' => true, + 'expected' => 'termAttrAlias.value NOT IN (4,3,42)', + ], + 'multiSelectPositiveEqual' => [ + 'frontendInput' => 'multiselect', + 'fieldValue' => 'positiveValue', + 'isNegation' => false, + 'expected' => 'termAttrAlias.value = positiveValue', + ], + 'multiSelectPositiveArray' => [ + 'frontendInput' => 'multiselect', + 'fieldValue' => [2, 3, 15], + 'isNegation' => false, + 'expected' => 'termAttrAlias.value IN (2,3,15)', + ], + 'multiSelectNegativeEqual' => [ + 'frontendInput' => 'multiselect', + 'fieldValue' => 'negativeValue', + 'isNegation' => true, + 'expected' => 'termAttrAlias.value != negativeValue', + ], + 'multiSelectNegativeArray' => [ + 'frontendInput' => 'multiselect', + 'fieldValue' => [4, 3, 42], + 'isNegation' => true, + 'expected' => 'termAttrAlias.value NOT IN (4,3,42)', + ], + ]; + } + public function testProcessNotStaticAttribute() { $expectedResult = 'search_index.entity_id IN (select entity_id from (TEST QUERY PART) as filter)'; diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php index 60a8bbbd47b827d32b4dc7c0994cb4422e66742b..f888803a1db621e6c9af3c07da658a9a8d32b5ee 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php @@ -167,15 +167,15 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase { $priceFilter = $this->createRangeFilter('static'); $query = $this->createFilterQuery($priceFilter); - $this->createAttributeMock('static', 'static', 'backend_table', 0); + $this->createAttributeMock('static', 'static', 'backend_table', 0, 'select'); $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); $this->select->expects($this->once()) ->method('joinLeft') ->with( - ['4111c4a3daddb5c5dba31cdac705114b' => 'backend_table'], - 'search_index.entity_id = 4111c4a3daddb5c5dba31cdac705114b.entity_id', + ['static_filter' => 'backend_table'], + 'search_index.entity_id = static_filter.entity_id', null ) ->willReturnSelf(); @@ -204,7 +204,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase public function testAddTermFilter() { - $this->createAttributeMock('color', null, null, 132, 0); + $this->createAttributeMock('color', null, null, 132, 'select', 0); $categoryIdsFilter = $this->createTermFilter('color'); $query = $this->createFilterQuery($categoryIdsFilter); $this->request->expects($this->once()) @@ -226,9 +226,9 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase public function testAddBoolQueryWithTermFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 0); - $this->createAttributeMock('should1', null, null, 102, 1); - $this->createAttributeMock('mustNot1', null, null, 103, 2); + $this->createAttributeMock('must1', null, null, 101, 'select', 0); + $this->createAttributeMock('should1', null, null, 102, 'select', 1); + $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createBoolQuery( [ @@ -280,9 +280,9 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase public function testAddBoolQueryWithTermAndPriceFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 0); - $this->createAttributeMock('should1', null, null, 102, 1); - $this->createAttributeMock('mustNot1', null, null, 103, 2); + $this->createAttributeMock('must1', null, null, 101, 'select', 0); + $this->createAttributeMock('should1', null, null, 102, 'select', 1); + $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createBoolQuery( [ $this->createFilterQuery($this->createTermFilter('must1')), @@ -342,9 +342,9 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase public function testAddBoolFilterWithTermFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 0); - $this->createAttributeMock('should1', null, null, 102, 1); - $this->createAttributeMock('mustNot1', null, null, 103, 2); + $this->createAttributeMock('must1', null, null, 101, 'select', 0); + $this->createAttributeMock('should1', null, null, 102, 'select', 1); + $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createFilterQuery( $this->createBoolFilter( [ @@ -397,9 +397,9 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase public function testAddBoolFilterWithBoolFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 0); - $this->createAttributeMock('should1', null, null, 102, 1); - $this->createAttributeMock('mustNot1', null, null, 103, 2); + $this->createAttributeMock('must1', null, null, 101, 'select', 0); + $this->createAttributeMock('should1', null, null, 102, 'select', 1); + $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createFilterQuery( $this->createBoolFilter( [ @@ -563,6 +563,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase * @param string $backendType * @param string $backendTable * @param int $attributeId + * @param string $frontendInput * @param int $positionInCollection */ private function createAttributeMock( @@ -570,10 +571,11 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $backendType = null, $backendTable = null, $attributeId = 120, + $frontendInput = 'select', $positionInCollection = 0 ) { $attribute = $this->getMockBuilder('\Magento\Catalog\Model\Resource\Eav\Attribute') - ->setMethods(['getBackendType', 'getBackendTable', 'getId']) + ->setMethods(['getBackendType', 'getBackendTable', 'getId', 'getFrontendInput']) ->disableOriginalConstructor() ->getMock(); $attribute->method('getId') @@ -582,6 +584,8 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase ->willReturn($backendType); $attribute->method('getBackendTable') ->willReturn($backendTable); + $attribute->method('getFrontendInput') + ->willReturn($frontendInput); $this->attributeCollection->expects($this->at($positionInCollection)) ->method('getItemByColumnValue') ->with('attribute_code', $code) diff --git a/app/code/Magento/CatalogSearch/view/frontend/layout/default.xml b/app/code/Magento/CatalogSearch/view/frontend/layout/default.xml index c04fedb21f0f325e1a96e05c03610ba5e0746325..892395ec75e3ec1735901258972bc4a589ca97eb 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/layout/default.xml +++ b/app/code/Magento/CatalogSearch/view/frontend/layout/default.xml @@ -15,6 +15,9 @@ <arguments> <argument name="label" xsi:type="string">Advanced Search</argument> <argument name="path" xsi:type="string">catalogsearch/advanced</argument> + <argument name="attributes" xsi:type="array"> + <item name="data-action" xsi:type="string">advanced-search</item> + </argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml index d2558ea32ae0a181fc5895594e21de6acb6a1451..4b84bb7c6e7d1d3a878f992e3c60e2d18f7fe6ee 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/link.phtml @@ -10,7 +10,7 @@ $helper = $this->helper('Magento\CatalogSearch\Helper\Data'); ?> <div class="nested"> - <a class="action advanced" href="<?php /* @escapeNotVerified */ echo $helper->getAdvancedSearchUrl(); ?>"> + <a class="action advanced" href="<?php /* @escapeNotVerified */ echo $helper->getAdvancedSearchUrl(); ?>" data-action="advanced-search"> <?php /* @escapeNotVerified */ echo __('Advanced Search'); ?> </a> </div> diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/Plugin/Import.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/Plugin/Import.php index d83d347f4b65ea687b61254cb8195b3e0c6fa739..45ac3244d56e2757656c499951124707a40f2a4d 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/Plugin/Import.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/Plugin/Import.php @@ -320,12 +320,14 @@ class Import $urls = []; foreach ($this->products as $productId => $productsByStores) { foreach ($productsByStores as $storeId => $product) { - $urls[] = $this->urlRewriteFactory->create() - ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) - ->setEntityId($productId) - ->setRequestPath($this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId)) - ->setTargetPath($this->productUrlPathGenerator->getCanonicalUrlPath($product)) - ->setStoreId($storeId); + if ($this->productUrlPathGenerator->getUrlPath($product)) { + $urls[] = $this->urlRewriteFactory->create() + ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) + ->setEntityId($productId) + ->setRequestPath($this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId)) + ->setTargetPath($this->productUrlPathGenerator->getCanonicalUrlPath($product)) + ->setStoreId($storeId); + } } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/Plugin/ImportTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/Plugin/ImportTest.php index 296a19e9374dac89b3c39fef8ab6c8f2df8275b4..b462b7b658031d59211567a0ee31d0d6166f2d27 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/Plugin/ImportTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/Plugin/ImportTest.php @@ -670,7 +670,7 @@ class ImportTest extends \PHPUnit_Framework_TestCase /** * Cover canonicalUrlRewriteGenerate(). */ - public function testCanonicalUrlRewriteGenerate() + public function testCanonicalUrlRewriteGenerateWithUrlPath() { $productId = 'product_id'; $requestPath = 'simple-product.html'; @@ -691,6 +691,10 @@ class ImportTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('getUrlPathWithSuffix') ->will($this->returnValue($requestPath)); + $this->productUrlPathGenerator + ->expects($this->once()) + ->method('getUrlPath') + ->will($this->returnValue('urlPath')); $this->productUrlPathGenerator ->expects($this->once()) ->method('getCanonicalUrlPath') @@ -734,6 +738,36 @@ class ImportTest extends \PHPUnit_Framework_TestCase ); } + /** + * Cover canonicalUrlRewriteGenerate(). + */ + public function testCanonicalUrlRewriteGenerateWithEmptyUrlPath() + { + $productId = 'product_id'; + $storeId = 10; + $product = $this + ->getMockBuilder('Magento\Catalog\Model\Product') + ->disableOriginalConstructor() + ->getMock(); + $productsByStores = [$storeId => $product]; + $products = [ + $productId => $productsByStores, + ]; + + $this->setPropertyValue($this->import, 'products', $products); + + $this->productUrlPathGenerator + ->expects($this->once()) + ->method('getUrlPath') + ->will($this->returnValue('')); + $this->urlRewriteFactory + ->expects($this->never()) + ->method('create'); + + $actualResult = $this->invokeMethod($this->import, 'canonicalUrlRewriteGenerate'); + $this->assertEquals([], $actualResult); + } + /** * Cover categoriesUrlRewriteGenerate(). */ diff --git a/app/code/Magento/Checkout/Block/Cart/Crosssell.php b/app/code/Magento/Checkout/Block/Cart/Crosssell.php index 2eb54a9bbddd1f97bd53816b149833a7b8e350a8..ba11d5c4aad2a22dd40d45f17b68ffe044af8445 100644 --- a/app/code/Magento/Checkout/Block/Cart/Crosssell.php +++ b/app/code/Magento/Checkout/Block/Cart/Crosssell.php @@ -199,8 +199,6 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct ); $this->_addProductAttributesAndPrices($collection); - $this->stockHelper->addInStockFilterToCollection($collection); - return $collection; } } diff --git a/app/code/Magento/Checkout/Block/Cart/Sidebar.php b/app/code/Magento/Checkout/Block/Cart/Sidebar.php index ed3a1cf671b1523a95efd0886776e384d9896fca..28a297da6780c2e63620b166ceb663719f61c8be 100644 --- a/app/code/Magento/Checkout/Block/Cart/Sidebar.php +++ b/app/code/Magento/Checkout/Block/Cart/Sidebar.php @@ -107,7 +107,7 @@ class Sidebar extends AbstractCart */ public function getUpdateItemQtyUrl() { - return $this->getUrl('checkout/sidebar/updateItemQty'); + return $this->getUrl('checkout/sidebar/updateItemQty', ['_secure' => $this->getRequest()->isSecure()]); } /** @@ -118,7 +118,7 @@ class Sidebar extends AbstractCart */ public function getRemoveItemUrl() { - return $this->getUrl('checkout/sidebar/removeItem'); + return $this->getUrl('checkout/sidebar/removeItem', ['_secure' => $this->getRequest()->isSecure()]); } /** diff --git a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php index 5fb823cc0c122234a2c95ced028ea6a92befa43b..d6c71a80bd6ab75a8c1cbb77831d54b222303b60 100644 --- a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php +++ b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php @@ -72,6 +72,13 @@ class AttributeMerger */ private $directoryHelper; + /** + * List of codes of countries that must be shown on the top of country list + * + * @var array + */ + private $topCountryCodes; + /** * @param AddressHelper $addressHelper * @param Session $customerSession @@ -88,6 +95,7 @@ class AttributeMerger $this->customerSession = $customerSession; $this->customerRepository = $customerRepository; $this->directoryHelper = $directoryHelper; + $this->topCountryCodes = $directoryHelper->getTopCountryCodes(); } /** @@ -177,7 +185,7 @@ class AttributeMerger ? $additionalConfig['sortOrder'] : $attributeConfig['sortOrder'], 'validation' => $this->mergeConfigurationNode('validation', $additionalConfig, $attributeConfig), - 'options' => isset($attributeConfig['options']) ? $attributeConfig['options'] : [], + 'options' => $this->getFieldOptions($attributeCode, $attributeConfig), 'filterBy' => isset($additionalConfig['filterBy']) ? $additionalConfig['filterBy'] : null, 'customEntry' => isset($additionalConfig['customEntry']) ? $additionalConfig['customEntry'] : null, 'visible' => isset($additionalConfig['visible']) ? $additionalConfig['visible'] : true, @@ -318,4 +326,46 @@ class AttributeMerger } return $this->customer; } + + /** + * Retrieve field options from attribute configuration + * + * @param string $attributeCode + * @param array $attributeConfig + * @return array + */ + protected function getFieldOptions($attributeCode, array $attributeConfig) + { + $options = isset($attributeConfig['options']) ? $attributeConfig['options'] : []; + return ($attributeCode == 'country_id') ? $this->orderCountryOptions($options) : $options; + } + + /** + * Order country options. Move top countries to the beginning of the list. + * + * @param array $countryOptions + * @return array + */ + protected function orderCountryOptions(array $countryOptions) + { + if (empty($this->topCountryCodes)) { + return $countryOptions; + } + + $headOptions = []; + $tailOptions = [[ + 'value' => 'delimiter', + 'label' => '──────────', + 'disabled' => true, + ]]; + foreach ($countryOptions as $countryOption) { + if (empty($countryOption['value']) || in_array($countryOption['value'], $this->topCountryCodes)) { + array_push($headOptions, $countryOption); + } else { + array_push($tailOptions, $countryOption); + } + + } + return array_merge($headOptions, $tailOptions); + } } diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php index 9c699dceaaf8066f48b55450469b709c12f607f4..5a8f71433bb3b73a8e8952c910e20f846595ab8f 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php @@ -45,10 +45,16 @@ class SidebarTest extends \PHPUnit_Framework_TestCase */ protected $checkoutSessionMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $requestMock; + protected function setUp() { $this->_objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->requestMock = $this->getMock('\Magento\Framework\App\RequestInterface'); $this->layoutMock = $this->getMock('\Magento\Framework\View\Layout', [], [], '', false); $this->checkoutSessionMock = $this->getMock('\Magento\Checkout\Model\Session', [], [], '', false); $this->urlBuilderMock = $this->getMock('\Magento\Framework\UrlInterface', [], [], '', false); @@ -64,7 +70,7 @@ class SidebarTest extends \PHPUnit_Framework_TestCase $contextMock = $this->getMock( '\Magento\Framework\View\Element\Template\Context', - ['getLayout', 'getUrlBuilder', 'getStoreManager', 'getScopeConfig'], + ['getLayout', 'getUrlBuilder', 'getStoreManager', 'getScopeConfig', 'getRequest'], [], '', false @@ -81,6 +87,9 @@ class SidebarTest extends \PHPUnit_Framework_TestCase $contextMock->expects($this->once()) ->method('getScopeConfig') ->will($this->returnValue($this->scopeConfigMock)); + $contextMock->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($this->requestMock)); $this->model = $this->_objectManager->getObject( 'Magento\Checkout\Block\Cart\Sidebar', @@ -135,10 +144,14 @@ class SidebarTest extends \PHPUnit_Framework_TestCase $valueMap = [ ['checkout/cart', [], $shoppingCartUrl], ['checkout', [], $checkoutUrl], - ['checkout/sidebar/updateItemQty', [], $updateItemQtyUrl], - ['checkout/sidebar/removeItem', [], $removeItemUrl] + ['checkout/sidebar/updateItemQty', ['_secure' => false], $updateItemQtyUrl], + ['checkout/sidebar/removeItem', ['_secure' => false], $removeItemUrl] ]; + $this->requestMock->expects($this->any()) + ->method('isSecure') + ->willReturn(false); + $this->urlBuilderMock->expects($this->exactly(4)) ->method('getUrl') ->willReturnMap($valueMap); diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml index be709afab70808baf806528231afa194d69157e8..ecdcc3f249c654666977439f684e991c0e282874 100644 --- a/app/code/Magento/Checkout/etc/frontend/di.xml +++ b/app/code/Magento/Checkout/etc/frontend/di.xml @@ -12,6 +12,9 @@ <argument name="secureUrlList" xsi:type="array"> <item name="checkout_index" xsi:type="string">/checkout</item> </argument> + <argument name="excludedUrlList" xsi:type="array"> + <item name="checkout_sidebar" xsi:type="string">/checkout/sidebar</item> + </argument> </arguments> </type> <type name="Magento\Framework\View\Layout"> diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml index 64b2521983f70f33f18f41560f870b2119b66c25..737751c3ee947f79a6a0c065b28e17dcbfc0557e 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml @@ -156,7 +156,7 @@ <item name="grand-total" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/grand-total</item> <item name="config" xsi:type="array"> - <item name="title" xsi:type="string">Grand Total</item> + <item name="title" xsi:type="string">Order Total</item> <item name="template" xsi:type="string">Magento_Checkout/cart/totals/grand-total</item> </item> </item> diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml index 837c084da6f1611149b1127f36c8115850986c98..f9d93d1bd90905b6dd1d81a4101449baf5a5156d 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml @@ -6,6 +6,9 @@ */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd"> + <head> + <script src="Magento_Checkout/js/checkout-loader.js"/> + </head> <body> <referenceContainer name="content"> <block class="Magento\Checkout\Block\Onepage" name="checkout.root" template="onepage.phtml" cacheable="false"> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml index 85025f95cc170dda43a92c2dfc4d027651ab0de3..3ab0cc0b8d1b1132d7d6582b65cc90507f1f6fce 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml @@ -9,7 +9,7 @@ ?> <div class="block discount" id="block-discount" data-mage-init='{"collapsible":{"openedState": "active", "saveState": false}}'> <div class="title" data-role="title"> - <strong id="block-discount-heading" role="heading" aria-level="2"><?php /* @escapeNotVerified */ echo __('Discount Codes') ?></strong> + <strong id="block-discount-heading" role="heading" aria-level="2"><?php /* @escapeNotVerified */ echo __('Apply Discount Code') ?></strong> </div> <div class="content" data-role="content" aria-labelledby="block-discount-heading"> <form id="discount-coupon-form" @@ -22,16 +22,16 @@ <div class="fieldset coupon<?php strlen($block->getCouponCode()) ? ' applied' : ''?>"> <input type="hidden" name="remove" id="remove-coupon" value="0" /> <div class="field"> - <label for="coupon_code" class="label"><span><?php /* @escapeNotVerified */ echo __('Enter your code') ?></span></label> + <label for="coupon_code" class="label"><span><?php /* @escapeNotVerified */ echo __('Enter discount code') ?></span></label> <div class="control"> - <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?php echo $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?php echo $block->escapeHtml(__('Enter your code'));?>" /> + <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?php echo $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?php echo $block->escapeHtml(__('Enter discount code'));?>" /> </div> </div> <div class="actions-toolbar"> <?php if (!strlen($block->getCouponCode())): ?> <div class="primary"> - <button class="action apply primary" type="button" value="<?php /* @escapeNotVerified */ echo __('Apply Coupon') ?>"> - <span><?php /* @escapeNotVerified */ echo __('Apply Coupon') ?></span> + <button class="action apply primary" type="button" value="<?php /* @escapeNotVerified */ echo __('Apply Discount') ?>"> + <span><?php /* @escapeNotVerified */ echo __('Apply Discount') ?></span> </button> </div> <?php else: ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml b/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml index b62f0856fdb34c5d7aebe099092085448bcd0635..ff81cfafb4ca0d230f9dc285a957f3901f540b01 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/onepage.phtml @@ -6,30 +6,35 @@ // @codingStandardsIgnoreFile ?> - <div id="checkout" data-bind="scope:'checkout'" class="checkout-container"> - <!-- ko template: getTemplate() --><!-- /ko --> - <script type="text/x-magento-init"> - { - "#checkout": { - "Magento_Ui/js/core/app": <?php /* @escapeNotVerified */ echo $block->getJsLayout();?> - } - } - </script> - <script> - window.checkoutConfig = <?php /* @escapeNotVerified */ echo \Zend_Json::encode($block->getCheckoutConfig()); ?>; - // Create aliases for customer.js model from customer module - window.isCustomerLoggedIn = window.checkoutConfig.isCustomerLoggedIn; - window.customerData = window.checkoutConfig.customerData; - </script> - <script> - require([ - 'mage/url', - 'Magento_Checkout/js/model/step-loader', - 'Magento_Ui/js/block-loader' - ], function(url, loader, blockLoader) { - loader.registerLoader(); - blockLoader("<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/loader-1.gif'); ?>"); - return url.setBaseUrl('<?php /* @escapeNotVerified */ echo $block->getBaseUrl();?>'); - }) - </script> +<div id="checkout" data-bind="scope:'checkout'" class="checkout-container"> + <div id="checkout-loader" data-role="checkout-loader" class="loading-mask"> + <div class="loader"> + <img src="<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/loader-1.gif'); ?>" + alt="<?php /* @escapeNotVerified */ echo __('Loading...'); ?>" + style="position: absolute;"> + </div> </div> + <!-- ko template: getTemplate() --><!-- /ko --> + <script type="text/x-magento-init"> + { + "#checkout": { + "Magento_Ui/js/core/app": <?php /* @escapeNotVerified */ echo $block->getJsLayout();?> + } + } + </script> + <script> + window.checkoutConfig = <?php /* @escapeNotVerified */ echo \Zend_Json::encode($block->getCheckoutConfig()); ?>; + // Create aliases for customer.js model from customer module + window.isCustomerLoggedIn = window.checkoutConfig.isCustomerLoggedIn; + window.customerData = window.checkoutConfig.customerData; + </script> + <script> + require([ + 'mage/url', + 'Magento_Ui/js/block-loader' + ], function(url, blockLoader) { + blockLoader("<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/loader-1.gif'); ?>"); + return url.setBaseUrl('<?php /* @escapeNotVerified */ echo $block->getBaseUrl();?>'); + }) + </script> +</div> diff --git a/app/code/Magento/Checkout/view/frontend/templates/success.phtml b/app/code/Magento/Checkout/view/frontend/templates/success.phtml index 83ba32c1985cf37915c6d2abe42af972557c79a0..6ed51a9f9ada643683fc3759843b2d6e96f508d4 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/success.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/success.phtml @@ -22,7 +22,7 @@ <div class="actions-toolbar"> <div class="primary"> - <a class="action primary continue" title="<?php /* @escapeNotVerified */ echo __('Continue Shopping') ?>" href="<?php /* @escapeNotVerified */ echo $block->getUrl() ?>"><span><?php /* @escapeNotVerified */ echo __('Continue Shopping') ?></span></a> + <a class="action primary continue" href="<?php /* @escapeNotVerified */ echo $block->getUrl() ?>"><span><?php /* @escapeNotVerified */ echo __('Continue Shopping') ?></span></a> </div> </div> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js index 99b14c50921dc383b4b109ace9ca6dee81a3f4a9..73398b2555aa1eab43f1e0c745819b2a2857e6f3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/place-order.js @@ -9,16 +9,17 @@ define( 'mage/storage', 'mage/url', 'Magento_Checkout/js/model/error-processor', - 'Magento_Customer/js/model/customer' + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/model/full-screen-loader' ], - function (quote, urlBuilder, storage, url, errorProcessor, customer) { + function (quote, urlBuilder, storage, url, errorProcessor, customer, fullScreenLoader) { 'use strict'; return function (paymentData, redirectOnSuccess, messageContainer) { var serviceUrl, payload; - redirectOnSuccess = redirectOnSuccess === false ? false : true; + redirectOnSuccess = redirectOnSuccess !== false; /** Checkout for guest and registered customer. */ if (!customer.isLoggedIn()) { @@ -39,8 +40,10 @@ define( billingAddress: quote.billingAddress() }; } + + fullScreenLoader.startLoader(); return storage.post( - serviceUrl, JSON.stringify(payload) + serviceUrl, JSON.stringify(payload), false ).done( function () { if (redirectOnSuccess) { @@ -50,6 +53,7 @@ define( ).fail( function (response) { errorProcessor.process(response, messageContainer); + fullScreenLoader.stopLoader(); } ); }; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js index a8f578f78b2fe490a8aad23b8205b44f0bf65f0b..85314e6a4478bf13a00c6a8679a9de60bf7de4ec 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/set-payment-information.js @@ -8,15 +8,15 @@ define( 'Magento_Checkout/js/model/url-builder', 'mage/storage', 'Magento_Checkout/js/model/error-processor', - 'Magento_Customer/js/model/customer' + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/model/full-screen-loader' ], - function (quote, urlBuilder, storage, errorProcessor, customer) { + function (quote, urlBuilder, storage, errorProcessor, customer, fullScreenLoader) { 'use strict'; - return function (messageContainer) { + return function (messageContainer, paymentData) { var serviceUrl, - payload, - paymentData = quote.paymentMethod(); + payload; /** * Checkout for guest and registered customer. @@ -39,8 +39,10 @@ define( billingAddress: quote.billingAddress() }; } + + fullScreenLoader.startLoader(); return storage.post( - serviceUrl, JSON.stringify(payload) + serviceUrl, JSON.stringify(payload), false ).done( function () { //do nothing @@ -48,6 +50,7 @@ define( ).fail( function (response) { errorProcessor.process(response, messageContainer); + fullScreenLoader.stopLoader(); } ); }; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/checkout-loader.js b/app/code/Magento/Checkout/view/frontend/web/js/checkout-loader.js new file mode 100644 index 0000000000000000000000000000000000000000..4c79735a252a03d9d8cf09a91b9f1e8b6bf15e3c --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/checkout-loader.js @@ -0,0 +1,35 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*jslint browser: true*/ +(function () { + 'use strict'; + + var checkInterval; + + checkInterval = setInterval(function () { + var checkoutContainer = document.getElementById('checkoutSteps'), + steps, + loaderContainer; + + //Return if checkout steps container not loaded + if (!checkoutContainer) { + return; + } + + //Checkout steps + steps = checkoutContainer.getElementsByTagName('li'); + + //Remove loader and clear update interval if content loaded + if (steps && steps.length > 0) { + clearInterval(checkInterval); + loaderContainer = document.getElementById('checkout-loader'); + + if (loaderContainer && loaderContainer.parentNode) { + loaderContainer.parentNode.removeChild(loaderContainer); + } + } + + }, 100); +})(); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js b/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js new file mode 100644 index 0000000000000000000000000000000000000000..bbbfb215e107e782b99340818b4b8092a819d271 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js @@ -0,0 +1,31 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*jshint browser:true jquery:true*/ +/*global alert*/ +define( + ['jquery'], + function ($) { + 'use strict'; + + var containerId = '#checkout'; + + return { + + /** + * Start full page loader action + */ + startLoader: function () { + $(containerId).trigger('processStart'); + }, + + /** + * Stop full page loader action + */ + stopLoader: function () { + $(containerId).trigger('processStop'); + } + }; + } +); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js index e9e63e280773c84372f2a42bcf9428f67febba77..58a7b8373affd68bd73fa717714d68d73771e51c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js @@ -11,11 +11,20 @@ define( 'mage/storage', 'Magento_Checkout/js/model/payment-service', 'Magento_Checkout/js/model/payment/method-converter', - 'Magento_Checkout/js/model/error-processor' + 'Magento_Checkout/js/model/error-processor', + 'Magento_Checkout/js/model/full-screen-loader' ], - function (ko, quote, resourceUrlManager, storage, paymentService, methodConverter, errorProcessor) { + function ( + ko, + quote, + resourceUrlManager, + storage, + paymentService, + methodConverter, + errorProcessor, + fullScreenLoader + ) { 'use strict'; - return { saveShippingInformation: function() { var payload = { @@ -26,6 +35,7 @@ define( } }; + fullScreenLoader.startLoader(); return storage.post( resourceUrlManager.getUrlForSetShippingInformation(quote), JSON.stringify(payload) @@ -33,10 +43,12 @@ define( function (response) { quote.setTotals(response.totals); paymentService.setPaymentMethods(methodConverter(response.payment_methods)); + fullScreenLoader.stopLoader(); } ).fail( function (response) { errorProcessor.process(response); + fullScreenLoader.stopLoader(); } ); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/step-loader.js b/app/code/Magento/Checkout/view/frontend/web/js/model/step-loader.js deleted file mode 100644 index 179fee04613429c98f22817228ec3c704ea8050b..0000000000000000000000000000000000000000 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/step-loader.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -/*jshint browser:true jquery:true*/ -/*global alert*/ -define(['jquery'], function($) { - return { - registerLoader: function() { - $(document).bind('ajaxSend', function() { - $('#checkout').trigger("processStart"); - }); - - $(document).bind('ajaxComplete', function() { - $('#checkout').trigger("processStop"); - }); - - $(document).bind('ajaxError', function() { - $('#checkout').trigger("processStop"); - }); - } - }; -}); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js index 4df8e6a7c28463a05396a2993319ce32cf5d8eb0..e85a5deb6dad278d2312e935ac0a039658154ea7 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js @@ -100,17 +100,23 @@ define( return activeItemIndex > requestedItemIndex; }, - navigateTo: function(code) { + navigateTo: function(code, scrollToElementId) { var sortedItems = steps.sort(this.sortItems); + var bodyElem = $.browser.safari || $.browser.chrome ? $("body") : $("html"); + scrollToElementId = scrollToElementId || null; + if (!this.isProcessed(code)) { return; } sortedItems.forEach(function(element) { if (element.code == code) { element.isVisible(true); - $('body').animate({scrollTop: $('#' + code).offset().top}, 0, function () { + bodyElem.animate({scrollTop: $('#' + code).offset().top}, 0, function () { window.location = window.checkoutConfig.checkoutUrl + "#" + code; }); + if (scrollToElementId && $('#' + scrollToElementId).length) { + bodyElem.animate({scrollTop: $('#' + scrollToElementId).offset().top}, 0); + } } else { element.isVisible(false); } @@ -129,9 +135,8 @@ define( if (steps().length > activeIndex + 1) { var code = steps()[activeIndex + 1].code; steps()[activeIndex + 1].isVisible(true); - $('body').animate({scrollTop: $('#' + code).offset().top}, 0, function () { - window.location = window.checkoutConfig.checkoutUrl + "#" + code; - }); + window.location = window.checkoutConfig.checkoutUrl + "#" + code; + document.body.scrollTop = document.documentElement.scrollTop = 0; } } }; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js index f4c2213022caf49f888f5155a94e41a81948c83c..7ffcee8d6e11e5f0dfc03f0fb3aea7a2bedad103 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js @@ -15,7 +15,7 @@ define([ $.widget('mage.regionUpdater', { options: { regionTemplate: - '<option value="<%- data.value %>" title="<%- data.title %>" <% if (data.isSelected) { %>selected="selected"<% } %>>' + + '<option value="<%- data.value %>" <% if (data.isSelected) { %>selected="selected"<% } %>>' + '<%- data.title %>' + '</option>', isRegionRequired: true, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/authentication.js b/app/code/Magento/Checkout/view/frontend/web/js/view/authentication.js index 06a3abe8642e651665c2a4b400bfeb828ff9afa8..89d8de2dd5f79fc0fe849b2b1c6cabd7e4a5d95b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/authentication.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/authentication.js @@ -11,9 +11,10 @@ define( 'Magento_Customer/js/action/login', 'Magento_Customer/js/model/customer', 'mage/validation', - 'Magento_Checkout/js/model/authentication-messages' + 'Magento_Checkout/js/model/authentication-messages', + 'Magento_Checkout/js/model/full-screen-loader' ], - function($, Component, loginAction, customer, validation, messageContainer) { + function($, Component, loginAction, customer, validation, messageContainer, fullScreenLoader) { 'use strict'; var checkoutConfig = window.checkoutConfig; @@ -44,7 +45,10 @@ define( if($(loginForm).validation() && $(loginForm).validation('isValid') ) { - loginAction(loginData, checkoutConfig.checkoutUrl, undefined, messageContainer); + fullScreenLoader.startLoader(); + loginAction(loginData, checkoutConfig.checkoutUrl, undefined, messageContainer).always(function() { + fullScreenLoader.stopLoader(); + }); } } }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js b/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js index 46effbced1a1442242049405955669311463f819..80da3cf1d6f68a85a2f9c529fe5eb395236b25d5 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js @@ -13,8 +13,9 @@ define([ 'Magento_Customer/js/action/login', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/checkout-data', + 'Magento_Checkout/js/model/full-screen-loader', 'mage/validation' -], function ($, Component, ko, customer, checkEmailAvailability, loginAction, quote, checkoutData) { +], function ($, Component, ko, customer, checkEmailAvailability, loginAction, quote, checkoutData, fullScreenLoader) { 'use strict'; var validatedEmail = checkoutData.getValidatedEmailValue(); @@ -147,7 +148,10 @@ define([ }); if (this.isPasswordVisible() && $(loginForm).validation() && $(loginForm).validation('isValid')) { - loginAction(loginData); + fullScreenLoader.startLoader(); + loginAction(loginData).always(function() { + fullScreenLoader.stopLoader(); + }); } } }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js index 16ede70554569481b1309d1d98470a4f53a2fe24..450cc17d55eeeba326ec24ce9a74b31ec0c95b7a 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js @@ -31,6 +31,11 @@ define( back: function() { sidebarModel.hide(); stepNavigator.navigateTo('shipping'); + }, + + backToShippingMethod: function() { + sidebarModel.hide(); + stepNavigator.navigateTo('shipping', 'opc-shipping_method'); } }); } diff --git a/app/code/Magento/Checkout/view/frontend/web/template/cart/totals/grand-total.html b/app/code/Magento/Checkout/view/frontend/web/template/cart/totals/grand-total.html index 02652743a383e36480a0182386feb1d79c96fb3d..e4ac4affabb341b664f3e868b6d298989fbf416b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/cart/totals/grand-total.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/cart/totals/grand-total.html @@ -8,7 +8,7 @@ <th class="mark" colspan="1" scope="row"> <strong data-bind="text: $t(title)"></strong> </th> - <td class="amount" data-th="Grand Total"> + <td class="amount" data-th="Order Total"> <strong><span class="price" data-bind="text: getValue()"></span></strong> </td> </tr> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 3331ff0296fdd305bb69b5dc89425a6cbc3047ec..9aedd4e7ffeebb46fbf852252537e59aea1bae6d 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -24,7 +24,7 @@ <div class="product-item-details"> <strong class="product-item-name"> <!-- ko if: product_has_url --> - <a data-bind="attr: {href: product_url, title: product_name}, text: product_name"></a> + <a data-bind="attr: {href: product_url}, text: product_name"></a> <!-- /ko --> <!-- ko ifnot: product_has_url --> <!-- ko text: product_name --><!-- /ko --> @@ -69,7 +69,8 @@ <!-- /ko --> <div class="details-qty qty"> - <span class="label" data-bind="i18n: 'Qty'"></span> + <label class="label" data-bind="i18n: 'Qty', attr: { + for: 'cart-item-'+item_id+'-qty'}"></label> <input data-bind="attr: { id: 'cart-item-'+item_id+'-qty', 'data-cart-item': item_id, diff --git a/app/code/Magento/Checkout/view/frontend/web/template/payment.html b/app/code/Magento/Checkout/view/frontend/web/template/payment.html index 51e8f46d8f16e28665078b271aea31c42fd9acc1..d73f8ff5cb3434721a62ef47bff0f27efb6fe9e9 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/payment.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/payment.html @@ -19,6 +19,9 @@ <form id="co-payment-form" class="form payments" novalidate="novalidate"> <input data-bind='attr: {value: getFormKey()}' type="hidden" name="form_key"/> <fieldset class="fieldset"> + <legend class="legend"> + <span data-bind="i18n: 'Payment Information'"></span> + </legend><br /> <!-- ko foreach: getRegion('beforeMethods') --> <!-- ko template: getTemplate() --><!-- /ko --> <!-- /ko --> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/form.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/form.html index f0257cfdfda449ac3984535ee2b6b980940365fd..d032b09f0a60419db8eb6eea678dd0a08b3f763c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/form.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/form.html @@ -8,7 +8,7 @@ <!-- ko foreach: getRegion('before-fields') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> - <fieldset id="shipping-new-address-form" class="fieldset address"> + <div id="shipping-new-address-form" class="fieldset address"> <!-- ko foreach: getRegion('additional-fieldsets') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> @@ -20,5 +20,5 @@ </label> </div> <!-- /ko --> - </fieldset> + </div> </form> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information.html index 3462a7cf295a6a3a58e72a92952dfa440e6067af..0e6ad40d317031dddbe7b120901432bf3e7bca8e 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information.html @@ -9,7 +9,7 @@ <div class="shipping-information"> <div class="ship-to"> <div class="shipping-information-title"> - <span data-bind="i18n: 'Ship To'"></span> + <span data-bind="i18n: 'Ship To:'"></span> <button class="action action-edit" data-bind="click: back"> <span data-bind="i18n: 'edit'"></span> </button> @@ -22,8 +22,8 @@ </div> <div class="ship-via"> <div class="shipping-information-title"> - <span data-bind="i18n: 'Ship Via'"></span> - <button class="action action-edit" data-bind="click: back"> + <span data-bind="i18n: 'Shipping Method:'"></span> + <button class="action action-edit" data-bind="click: backToShippingMethod"> <span data-bind="i18n: 'edit'"></span> </button> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping.html index c529aab467b2fc28720e0a494c909ce63f4d394a..7c981e83bc97859cef4e11ca96ebf8b679262f48 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping.html @@ -68,7 +68,7 @@ <table class="table-checkout-shipping-method"> <thead> <tr class="row"> - <th class="col"> </th> + <th class="col col-method" data-bind="i18n: 'Select Method'"></th> <th class="col col-price" data-bind="i18n: 'Price'"></th> <th class="col col-method" data-bind="i18n: 'Method Title'"></th> <th class="col col-carrier" data-bind="i18n: 'Carrier Title'"></th> @@ -78,20 +78,28 @@ <!--ko foreach: { data: rates(), as: 'method'}--> <tr class="row" data-bind="click: $parent.selectShippingMethod"> - <td class="col"> + <td class="col col-method"> <!-- ko ifnot: method.error_message --> <!-- ko if: $parent.rates().length == 1 --> <input name="shipping_method" class="radio" type="radio" - data-bind="attr: {checked: $parent.rates().length == 1, 'value' : method.carrier_code + '_' + method.method_code, 'id': 's_method_' + method.method_code}" /> + data-bind="attr: { + checked: $parent.rates().length == 1, + 'value' : method.carrier_code + '_' + method.method_code, + 'id': 's_method_' + method.method_code, + 'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code + }" /> <!-- /ko --> <!--ko ifnot: ($parent.rates().length == 1)--> <input name="shipping_method" type="radio" data-bind=" value: method.carrier_code + '_' + method.method_code, checked: $parent.isSelected, - attr: {'id': 's_method_' + method.carrier_code + '_' + method.method_code}, + attr: { + 'id': 's_method_' + method.carrier_code + '_' + method.method_code, + 'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code + }, click: $parent.selectShippingMethod" class="radio"/> <!--/ko--> @@ -102,8 +110,12 @@ <!-- ko template: getTemplate() --><!-- /ko --> <!-- /ko --> </td> - <td class="col col-method" data-bind="i18n: method.method_title"></td> - <td class="col col-carrier" data-bind="i18n: method.carrier_title"></td> + + <td class="col col-method" + data-bind="i18n: method.method_title, attr: {'id': 'label_method_' + method.method_code + '_' + method.carrier_code}"></td> + + <td class="col col-carrier" + data-bind="i18n: method.carrier_title, attr: {'id': 'label_carrier_' + method.method_code + '_' + method.carrier_code}"></td> </tr> <!-- ko if: method.error_message --> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html index b51a70273b2505c5b16eea1fe93b6548311fc1f3..d4f0994a38e5a88e32b08480c2311d5712eb4e15 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/cart-items.html @@ -8,7 +8,12 @@ <div class="block items-in-cart" data-bind="mageInit: {'collapsible':{'openedState': 'active'}}"> <div class="title" data-role="title"> <strong role="heading"><span data-bind="text: getItemsQty()"></span> + <!-- ko if: getItemsQty() == 1 --> + <!-- ko i18n: 'Item in Cart' --><!-- /ko --> + <!-- /ko --> + <!-- ko if: getItemsQty() > 1 --> <!-- ko i18n: 'Items in Cart' --><!-- /ko --> + <!-- /ko --> </strong> </div> <div class="content minicart-items" data-role="content"> @@ -32,7 +37,12 @@ <div class="block items-in-cart" data-bind="mageInit: {'collapsible':{'openedState': 'active', 'active': true}}"> <div class="title" data-role="title"> <strong role="heading"><span data-bind="text: getItemsQty()"></span> + <!-- ko if: getItemsQty() == 1 --> + <!-- ko i18n: 'Item in Cart' --><!-- /ko --> + <!-- /ko --> + <!-- ko if: getItemsQty() > 1 --> <!-- ko i18n: 'Items in Cart' --><!-- /ko --> + <!-- /ko --> </strong> </div> <div class="content minicart-items" data-role="content"> @@ -51,4 +61,4 @@ </div> </div> </div> -<!-- /ko --> \ No newline at end of file +<!-- /ko --> diff --git a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_listing.xml b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_listing.xml index 3984099e1e81c1bf7faac1fc4f395dc8e5af650e..342d4d0a4b83efe2dc56ce1f29586bffcdf603ab 100644 --- a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_listing.xml +++ b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_page_listing.xml @@ -43,6 +43,7 @@ <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">ui/grid/toolbar</item> + <item name="stickyTmpl" xsi:type="string">ui/grid/sticky/toolbar</item> </item> </argument> <bookmark name="bookmarks"> @@ -692,4 +693,13 @@ </argument> </column> </columns> + <container name="sticky"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Ui/js/grid/sticky/sticky</item> + <item name="toolbarProvider" xsi:type="string">cms_page_listing.cms_page_listing.listing_top</item> + <item name="listingProvider" xsi:type="string">cms_page_listing.cms_page_listing.cms_page_columns</item> + </item> + </argument> + </container> </listing> diff --git a/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php b/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php index 300b9bd89f898fb92bf574df310b9a1858cd6b88..21549a5862ae9420010f50d906cf26d9029cccf7 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php +++ b/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php @@ -18,12 +18,37 @@ class Custom extends \Magento\Framework\App\Config\Value const CONFIG_SCOPE_ID = 0; const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url'; - const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url'; - const XML_PATH_UNSECURE_BASE_LINK_URL = 'web/unsecure/base_link_url'; - const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url'; + const XML_PATH_CURRENCY_OPTIONS_BASE = 'currency/options/base'; + const XML_PATH_ADMIN_SECURITY_USEFORMKEY = 'admin/security/use_form_key'; + const XML_PATH_MAINTENANCE_MODE = 'maintenance_mode'; + const XML_PATH_WEB_COOKIE_COOKIE_LIFETIME = 'web/cookie/cookie_lifetime'; + const XML_PATH_WEB_COOKIE_COOKE_PATH = 'web/cookie/cookie_path'; + const XML_PATH_WEB_COOKIE_COOKIE_DOMAIN = 'web/cookie/cookie_domain'; + const XML_PATH_WEB_COOKIE_HTTPONLY = 'web/cookie/cookie_httponly'; + const XML_PATH_WEB_COOKIE_RESTRICTION = 'web/cookie/cookie_restriction'; + const XML_PATH_GENERAL_LOCALE_TIMEZONE = 'general/locale/timezone'; + const XML_PATH_GENERAL_LOCALE_CODE = 'general/locale/code'; + const XML_PATH_GENERAL_COUNTRY_DEFAULT = 'general/country/default'; + const XML_PATH_SYSTEM_BACKUP_ENABLED = 'system/backup/enabled'; + const XML_PATH_DEV_JS_MERGE_FILES = 'dev/js/merge_files'; + const XML_PATH_DEV_JS_MINIFY_FILES = 'dev/js/minify_files'; + const XML_PATH_DEV_CSS_MERGE_CSS_FILES = 'dev/css/merge_css_files'; + const XML_PATH_DEV_CSS_MINIFY_FILES = 'dev/css/minify_files'; + const XML_PATH_DEV_IMAGE_DEFAULT_ADAPTER = 'dev/image/default_adapter'; + const XML_PATH_WEB_SESSION_USE_FRONTEND_SID = 'web/session/use_frontend_sid'; + const XML_PATH_WEB_SESSION_USE_HTTP_X_FORWARDED_FOR = 'web/session/use_http_x_forwarded_for'; + const XML_PATH_WEB_SESSION_USE_HTTP_VIA = 'web/session/use_http_via'; + const XML_PATH_WEB_SESSION_USE_REMOTE_ADDR = 'web/session/use_remote_addr'; + const XML_PATH_WEB_SESSION_USE_HTTP_USER_AGENT = 'web/session/use_http_user_agent'; + const XML_PATH_CATALOG_FRONTEND_FLAT_CATALOG_CATEGORY = 'catalog/frontend/flat_catalog_category'; + const XML_PATH_CATALOG_FRONTEND_FLAT_CATALOG_PRODUCT = 'catalog/frontend/flat_catalog_product'; + const XML_PATH_TAX_WEEE_ENABLE = 'tax/weee/enable'; + const XML_PATH_CATALOG_SEARCH_ENGINE = 'catalog/search/engine'; + const XML_PATH_CARRIERS = 'carriers'; + const XML_PATH_PAYMENT = 'payment'; /* @var \Magento\Framework\App\Config\Storage\WriterInterface */ protected $_configWriter; diff --git a/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php b/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php index 118108189fd5f6052a607e85b7824f38e72a7a56..a7271b7441e2609acb4c54e5f1a0fe9aa0c989df 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php +++ b/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php @@ -51,11 +51,11 @@ class Sorting extends \Magento\Config\Model\Config\Structure\AbstractMapper { $sortIndexA = 0; if ($this->_hasValue('sortOrder', $elementA)) { - $sortIndexA = intval($elementA['sortOrder']); + $sortIndexA = floatval($elementA['sortOrder']); } $sortIndexB = 0; if ($this->_hasValue('sortOrder', $elementB)) { - $sortIndexB = intval($elementB['sortOrder']); + $sortIndexB = floatval($elementB['sortOrder']); } if ($sortIndexA == $sortIndexB) { diff --git a/app/code/Magento/Config/etc/system.xsd b/app/code/Magento/Config/etc/system.xsd index 4703d178f1a9e5c0c18df0eb67063d5bc9b310c5..3585f499ddc2429197369c0babbb8de74927ba40 100644 --- a/app/code/Magento/Config/etc/system.xsd +++ b/app/code/Magento/Config/etc/system.xsd @@ -48,7 +48,7 @@ <xs:attributeGroup name="tabAttributeGroup"> <xs:attribute name="id" type="typeId" use="required" /> <xs:attribute name="translate" type="xs:string" use="optional" /> - <xs:attribute name="sortOrder" type="xs:int" use="optional" /> + <xs:attribute name="sortOrder" type="xs:float" use="optional" /> <xs:attribute name="class" type="xs:string" use="optional" /> </xs:attributeGroup> @@ -56,7 +56,7 @@ <xs:attribute name="id" type="typeId" use="required" /> <xs:attribute name="translate" type="xs:string" use="optional" /> <xs:attribute name="type" type="xs:string" use="optional" /> - <xs:attribute name="sortOrder" type="xs:int" use="optional" /> + <xs:attribute name="sortOrder" type="xs:float" use="optional" /> <xs:attribute name="showInDefault" type="xs:int" use="optional" /> <xs:attribute name="showInStore" type="xs:int" use="optional" /> <xs:attribute name="showInWebsite" type="xs:int" use="optional" /> diff --git a/app/code/Magento/Config/etc/system_file.xsd b/app/code/Magento/Config/etc/system_file.xsd index 89c8f2db61d9d649255c44d261a128cb98ee384f..0bbc99c6a07f7da7e44237e4f8bc716bf47bde92 100644 --- a/app/code/Magento/Config/etc/system_file.xsd +++ b/app/code/Magento/Config/etc/system_file.xsd @@ -49,7 +49,7 @@ <xs:attributeGroup name="tabAttributeGroup"> <xs:attribute name="id" type="typeId" use="required" /> <xs:attribute name="translate" type="xs:string" use="optional" /> - <xs:attribute name="sortOrder" type="xs:int" use="optional" /> + <xs:attribute name="sortOrder" type="xs:float" use="optional" /> <xs:attribute name="class" type="xs:string" use="optional" /> </xs:attributeGroup> @@ -57,7 +57,7 @@ <xs:attribute name="id" type="typeId" use="required" /> <xs:attribute name="translate" type="xs:string" use="optional" /> <xs:attribute name="type" type="xs:string" use="optional" /> - <xs:attribute name="sortOrder" type="xs:int" use="optional" /> + <xs:attribute name="sortOrder" type="xs:float" use="optional" /> <xs:attribute name="showInDefault" type="xs:int" use="optional" /> <xs:attribute name="showInStore" type="xs:int" use="optional" /> <xs:attribute name="showInWebsite" type="xs:int" use="optional" /> diff --git a/app/code/Magento/ConfigurableProduct/Api/Data/ConfigurableItemOptionValueInterface.php b/app/code/Magento/ConfigurableProduct/Api/Data/ConfigurableItemOptionValueInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..3aad747e22a608f73f45dae0c636518a869f94ba --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Api/Data/ConfigurableItemOptionValueInterface.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Api\Data; + +/** + * Interface ConfigurableItemOptionValueInterface + * @api + */ +interface ConfigurableItemOptionValueInterface extends \Magento\Framework\Api\ExtensibleDataInterface +{ + /**#@+ + * Constants defined for keys of array, makes typos less likely + */ + const OPTION_ID = 'option_id'; + + const OPTION_VALUE = 'option_value'; + + /**#@-*/ + + /** + * Get option SKU + * + * @return string + */ + public function getOptionId(); + + /** + * Set option SKU + * + * @param string $value + * @return void + */ + public function setOptionId($value); + + /** + * Get item id + * + * @return int|null + */ + public function getOptionValue(); + + /** + * Set item id + * + * @param int|null $value + * @return void + */ + public function setOptionValue($value); + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/ProductTemplate/Form.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/ProductTemplate/Form.php new file mode 100644 index 0000000000000000000000000000000000000000..09370fdce3e72ac650a640a1096bf6b62b8582a4 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/ProductTemplate/Form.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * Product attribute set selector form + */ +namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\ProductTemplate; + +class Form extends \Magento\Backend\Block\Widget\Form\Generic +{ + /** + * @var \Magento\Eav\Model\Entity\Attribute\SetFactory + */ + protected $attributeSetRepository; + + /** + * @var \Magento\Catalog\Model\Product\AttributeSet\Options + */ + protected $attributeSetOptions; + + /** + * @param \Magento\Backend\Block\Template\Context $context + * @param \Magento\Framework\Registry $registry + * @param \Magento\Framework\Data\FormFactory $formFactory + * @param \Magento\Catalog\Api\AttributeSetRepositoryInterface $attributeSetRepository + * @param \Magento\Catalog\Model\Product\AttributeSet\Options $attributeSetOptions + * @param array $data + */ + public function __construct( + \Magento\Backend\Block\Template\Context $context, + \Magento\Framework\Registry $registry, + \Magento\Framework\Data\FormFactory $formFactory, + \Magento\Catalog\Api\AttributeSetRepositoryInterface $attributeSetRepository, + \Magento\Catalog\Model\Product\AttributeSet\Options $attributeSetOptions, + array $data = [] + ) { + $this->attributeSetRepository = $attributeSetRepository; + $this->attributeSetOptions = $attributeSetOptions; + parent::__construct($context, $registry, $formFactory, $data); + } + + /** + * Prepares attribute set form + * + * @return void + */ + protected function _prepareForm() + { + /** @var \Magento\Framework\Data\Form $form */ + $form = $this->_formFactory->create(); + $fieldset = $form->addFieldset('product_template_selecte', []); + $fieldset->addField( + 'current-affected-attribute-set', + 'radio', + [ + 'after_element_html' => __( + 'Add configurable attributes to the current Product Template ("%1")', + sprintf('<span data-role="name-container">%s</span>', $this->getCurrentAttributeSetName()) + ), + 'name' => 'affected-attribute-set', + 'class' => 'admin__control-radio', + 'checked' => true, + 'value' => 'current' + ] + ); + $fieldset->addField( + 'new-affected-attribute-set', + 'radio', + [ + 'after_element_html' => __('Add configurable attributes to the new Product Template based on current'), + 'name' => 'affected-attribute-set', + 'class' => 'admin__control-radio', + 'value' => 'new' + ] + ); + $fieldset->addField( + 'new-attribute-set-name', + 'text', + [ + 'label' => __('New product template name'), + 'name' => 'new-attribute-set-name', + 'required' => true, + 'css_class' => 'no-display', + 'field_extra_attributes' => 'data-role="affected-attribute-set-new-name-container"', + 'value' => '' + ] + ); + $fieldset->addField( + 'existing-affected-attribute-set', + 'radio', + [ + 'after_element_html' => __('Add configurable attributes to the existing Product Template'), + 'name' => 'affected-attribute-set', + 'required' => true, + 'class' => 'admin__control-radio no-display', + 'value' => 'existing' + ] + ); + $fieldset->addField( + 'choose-affected-attribute-set', + 'select', + [ + 'label' => __('Choose existing Product Template'), + 'name' => 'attribute-set-name', + 'required' => true, + 'css_class' => 'no-display', + 'field_extra_attributes' => 'data-role="affected-attribute-set-existing-name-container"', + 'values' => $this->attributeSetOptions->toOptionArray() + ] + ); + + $form->setUseContainer(true); + $this->setForm($form); + } + + + /** + * @return string + */ + protected function getCurrentAttributeSetName() + { + return $this->attributeSetRepository->get( + $this->_coreRegistry->registry('current_product')->getAttributeSetId() + )->getAttributeSetName(); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config.php index 500f85db85299e9b140690f3bb2911c9df46c445..b24b9231c727362417bd55a0cfa1e245b761cb17 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config.php +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config.php @@ -20,64 +20,24 @@ class Config extends Widget implements TabInterface */ protected $_template = 'catalog/product/edit/super/config.phtml'; - /** - * Catalog data - * - * @var \Magento\Catalog\Helper\Data - */ - protected $_catalogData = null; - /** * Core registry * * @var \Magento\Framework\Registry */ - protected $_coreRegistry = null; - - /** - * @var Configurable - */ - protected $_configurableType; - - /** - * @var \Magento\Framework\Locale\CurrencyInterface - */ - protected $_localeCurrency; - - /** - * @var \Magento\Framework\Json\EncoderInterface - */ - protected $_jsonEncoder; - - /** @var \Magento\Catalog\Helper\Image */ - protected $image; + protected $_coreRegistry; /** * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder - * @param Configurable $configurableType - * @param \Magento\Catalog\Helper\Data $catalogData * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Framework\Locale\CurrencyInterface $localeCurrency - * @param \Magento\Catalog\Helper\Image $image * @param array $data */ public function __construct( \Magento\Backend\Block\Template\Context $context, - \Magento\Framework\Json\EncoderInterface $jsonEncoder, - Configurable $configurableType, - \Magento\Catalog\Helper\Data $catalogData, \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\Locale\CurrencyInterface $localeCurrency, - \Magento\Catalog\Helper\Image $image, array $data = [] ) { - $this->_configurableType = $configurableType; $this->_coreRegistry = $coreRegistry; - $this->_catalogData = $catalogData; - $this->_jsonEncoder = $jsonEncoder; - $this->_localeCurrency = $localeCurrency; - $this->image = $image; parent::__construct($context, $data); } @@ -106,37 +66,6 @@ class Config extends Widget implements TabInterface return 'ajax'; } - /** - * Check block is readonly - * - * @return bool - */ - public function isReadonly() - { - return (bool)$this->getProduct()->getCompositeReadonly(); - } - - /** - * Check whether attributes of configurable products can be editable - * - * @return bool - */ - public function isAttributesConfigurationReadonly() - { - return (bool)$this->getProduct()->getAttributesConfigurationReadonly(); - } - - /** - * Check whether prices of configurable products can be editable - * - * @return bool - */ - public function isAttributesPricesReadonly() - { - return $this->getProduct()->getAttributesConfigurationReadonly() || - $this->_catalogData->isPriceGlobal() && $this->isReadonly(); - } - /** * Retrieve currently edited product object * @@ -147,79 +76,6 @@ class Config extends Widget implements TabInterface return $this->_coreRegistry->registry('current_product'); } - /** - * Retrieve attributes data - * - * @return array - */ - public function getAttributes() - { - if (!$this->hasData('attributes')) { - $attributes = (array)$this->_configurableType->getConfigurableAttributesAsArray($this->getProduct()); - $this->setData('attributes', $attributes); - } - return $this->getData('attributes'); - } - - /** - * Retrieve Links in JSON format - * - * @return string - */ - public function getLinksJson() - { - $products = $this->_configurableType->getUsedProducts($this->getProduct()); - if (!$products) { - return '{}'; - } - $data = []; - foreach ($products as $product) { - $data[$product->getId()] = $this->getConfigurableSettings($product); - } - return $this->_jsonEncoder->encode($data); - } - - /** - * Retrieve configurable settings - * - * @param Product $product - * @return array - */ - public function getConfigurableSettings($product) - { - $data = []; - $attributes = $this->_configurableType->getUsedProductAttributes($this->getProduct()); - foreach ($attributes as $attribute) { - $data[] = [ - 'attribute_id' => $attribute->getId(), - 'label' => $product->getAttributeText($attribute->getAttributeCode()), - 'value_index' => $product->getData($attribute->getAttributeCode()), - ]; - } - - return $data; - } - - /** - * Retrieve Grid child HTML - * - * @return string - */ - public function getGridHtml() - { - return $this->getChildHtml('grid'); - } - - /** - * Retrieve Grid JavaScript object name - * - * @return string - */ - public function getGridJsObject() - { - return $this->getChildBlock('grid')->getJsObjectName(); - } - /** * Retrieve Tab label * @@ -227,7 +83,7 @@ class Config extends Widget implements TabInterface */ public function getTabLabel() { - return __('Associated Products'); + return __('Configurations'); } /** @@ -237,7 +93,7 @@ class Config extends Widget implements TabInterface */ public function getTabTitle() { - return __('Associated Products'); + return __('Configurations'); } /** @@ -260,18 +116,6 @@ class Config extends Widget implements TabInterface return false; } - /** - * Get list of used attributes - * - * @return array - */ - public function getSelectedAttributes() - { - return $this->getProduct()->getTypeId() == Configurable::TYPE_CODE ? array_filter( - $this->_configurableType->getUsedProductAttributes($this->getProduct()) - ) : []; - } - /** * Get parent tab code * @@ -283,22 +127,19 @@ class Config extends Widget implements TabInterface } /** - * Get base application currency - * - * @return \Zend_Currency + * @return bool */ - public function getBaseCurrency() + public function isConfigurableProduct() { - return $this->_localeCurrency->getCurrency( - $this->_scopeConfig->getValue(\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, 'default') - ); + return $this->getProduct()->getTypeId() === Configurable::TYPE_CODE || $this->getRequest()->has('attributes'); } /** - * @return string + * @return $this */ - public function getNoImageUrl() + protected function _prepareLayout() { - return $this->image->getDefaultPlaceholderUrl('thumbnail'); + $this->setData('opened', $this->isConfigurableProduct()); + return parent::_prepareLayout(); } } diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Attribute.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Attribute.php deleted file mode 100644 index 5dff74d202c39fb8432ef5e46e739199f57dd93d..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Attribute.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Renderer for attribute block - */ -namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config; - -class Attribute extends \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config -{ - /** - * Render block - * - * @param array $arguments - * @return string - */ - public function render(array $arguments) - { - $this->assign($arguments); - return $this->toHtml(); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/AssociatedProduct.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/AssociatedProduct.php deleted file mode 100644 index 0ba0e6577966b42359de35eda17b232358a8f3f4..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/AssociatedProduct.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid; - -/** - * Associated Product Grid - */ -class AssociatedProduct extends \Magento\Backend\Block\Widget\Grid\Container -{ - /** - * @return bool - */ - public function isHasRows() - { - /** @var $grid \Magento\Backend\Block\Widget\Grid */ - $grid = $this->getChildBlock('grid'); - return (bool)$grid->getPreparedCollection()->getSize(); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php deleted file mode 100644 index 5a426635880c60f2bb4937b56af4921c96f58615..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Filter/Inventory.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Configurable product associated products in stock filter - */ -namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\Filter; - -use Magento\Backend\Block\Widget\Grid\Column\Filter\Select; - -class Inventory extends Select -{ - /** - * @return array - */ - protected function _getOptions() - { - return [ - ['value' => '', 'label' => ''], - ['value' => 1, 'label' => __('In Stock')], - ['value' => 0, 'label' => __('Out of Stock')] - ]; - } -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Renderer/Checkbox.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Renderer/Checkbox.php deleted file mode 100644 index c95d0f2359d0fc11cfd80d2b2e36c5c34f6a1e2b..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Renderer/Checkbox.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php -/** - * Adminhtml catalog super product link grid checkbox renderer - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\Renderer; - -class Checkbox extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Checkbox -{ - /** - * @var \Magento\Framework\Json\EncoderInterface - */ - protected $_jsonEncoder; - - /** - * @param \Magento\Backend\Block\Context $context - * @param \Magento\Backend\Block\Widget\Grid\Column\Renderer\Options\Converter $converter - * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder - * @param array $data - */ - public function __construct( - \Magento\Backend\Block\Context $context, - \Magento\Backend\Block\Widget\Grid\Column\Renderer\Options\Converter $converter, - \Magento\Framework\Json\EncoderInterface $jsonEncoder, - array $data = [] - ) { - $this->_jsonEncoder = $jsonEncoder; - parent::__construct($context, $converter, $data); - } - - /** - * Renders grid column - * - * @param \Magento\Framework\DataObject $row - * @return string - */ - public function render(\Magento\Framework\DataObject $row) - { - $result = parent::render($row); - return $result . '<input type="hidden" class="value-json" value="' . htmlspecialchars( - $this->getAttributesJson($row) - ) . '" />'; - } - - /** - * Get attributes json - * - * @param \Magento\Framework\DataObject $row - * @return string - */ - public function getAttributesJson(\Magento\Framework\DataObject $row) - { - if (!$this->getColumn()->getAttributes()) { - return '[]'; - } - - $result = []; - foreach ($this->getColumn()->getAttributes() as $attribute) { - $productAttribute = $attribute->getProductAttribute(); - if ($productAttribute->getSourceModel()) { - $label = $productAttribute->getSource()->getOptionText( - $row->getData($productAttribute->getAttributeCode()) - ); - } else { - $label = $row->getData($productAttribute->getAttributeCode()); - } - $item = []; - $item['label'] = $label; - $item['attribute_id'] = $productAttribute->getId(); - $item['value_index'] = $row->getData($productAttribute->getAttributeCode()); - $result[] = $item; - } - - return $this->_jsonEncoder->encode($result); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Renderer/Inventory.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Renderer/Inventory.php deleted file mode 100644 index 8a3668cf9ac525d8bbea118d50e20fc801a89856..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Grid/Renderer/Inventory.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Configurable product assocciated products grid in stock renderer - */ -namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\Renderer; - -class Inventory extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer -{ - /** - * Renders grid column value - * - * @param \Magento\Framework\DataObject $row - * @return string - */ - public function render(\Magento\Framework\DataObject $row) - { - $inStock = $this->_getValue($row); - return $inStock ? __('In Stock') : __('Out of Stock'); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Matrix.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Matrix.php index 1296e271fa04c71c91b0f1d7a1c2ac4a9de4b471..0d91e23db469c1466e8443c049bbd79155cc7458 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Matrix.php +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/Matrix.php @@ -23,23 +23,13 @@ class Matrix extends \Magento\Backend\Block\Template * * @var \Magento\Framework\Registry */ - protected $_coreRegistry = null; + protected $_coreRegistry; /** * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable */ protected $_configurableType; - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ - protected $_applicationConfig; - - /** - * @var \Magento\Framework\Locale\CurrencyInterface - */ - protected $_localeCurrency; - /** * @var \Magento\CatalogInventory\Api\StockRegistryInterface */ @@ -55,52 +45,50 @@ class Matrix extends \Magento\Backend\Block\Template */ protected $productRepository; + /** @var \Magento\Catalog\Helper\Image */ + protected $image; + + /** @var null|array */ + private $productMatrix; + + /** @var null|array */ + private $productAttributes; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurableType - * @param \Magento\Catalog\Model\Config $config * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Framework\Locale\CurrencyInterface $localeCurrency * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry * @param \Magento\ConfigurableProduct\Model\Product\Type\VariationMatrix $variationMatrix * @param ProductRepositoryInterface $productRepository + * @param \Magento\Catalog\Helper\Image $image * @param array $data */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurableType, - \Magento\Catalog\Model\Config $config, \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\Locale\CurrencyInterface $localeCurrency, \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, \Magento\ConfigurableProduct\Model\Product\Type\VariationMatrix $variationMatrix, ProductRepositoryInterface $productRepository, + \Magento\Catalog\Helper\Image $image, array $data = [] ) { parent::__construct($context, $data); $this->_configurableType = $configurableType; - $this->_config = $config; $this->_coreRegistry = $coreRegistry; - $this->_localeCurrency = $localeCurrency; $this->stockRegistry = $stockRegistry; - parent::__construct($context, $data); $this->variationMatrix = $variationMatrix; $this->productRepository = $productRepository; + $this->image = $image; } /** - * Retrieve price rendered according to current locale and currency settings - * - * @param int|float $price * @return string */ - public function renderPrice($price) + public function getCurrencySymbol() { - return $this->_localeCurrency->getCurrency( - $this->_scopeConfig->getValue(\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, 'default') - )->toCurrency( - sprintf('%f', $price) - ); + return $this->_storeManager->getStore()->getCurrentCurrency()->getCurrencySymbol(); } /** @@ -168,7 +156,7 @@ class Matrix extends \Magento\Backend\Block\Template * * @return array */ - public function getUsedAttributes() + protected function getUsedAttributes() { return $this->_configurableType->getUsedProductAttributes($this->getProduct()); } @@ -178,7 +166,7 @@ class Matrix extends \Magento\Backend\Block\Template * * @return Product[] */ - public function getAssociatedProducts() + protected function getAssociatedProducts() { $productByUsedAttributes = []; foreach ($this->_getAssociatedProducts() as $product) { @@ -217,20 +205,6 @@ class Matrix extends \Magento\Backend\Block\Template return $products; } - /** - * Get html class for attribute - * - * @param string $code - * @return string - */ - public function getAttributeFrontendClass($code) - { - /** @var $attribute \Magento\Catalog\Model\Resource\Eav\Attribute */ - $attribute = $this->_config->getAttribute(Product::ENTITY, $code); - return $attribute instanceof - \Magento\Eav\Model\Entity\Attribute\AbstractAttribute ? $attribute->getFrontend()->getClass() : ''; - } - /** * Get url to upload files * @@ -264,4 +238,102 @@ class Matrix extends \Magento\Backend\Block\Template } return ''; } + + /** + * @return array|null + */ + public function getProductMatrix() + { + if ($this->productMatrix === null) { + $this->prepareVariations(); + } + return $this->productMatrix; + } + + /** + * @return array|null + */ + public function getProductAttributes() + { + if ($this->productAttributes === null) { + $this->prepareVariations(); + } + return $this->productAttributes; + } + + /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @return void + * TODO: move to class + */ + protected function prepareVariations() + { + $variations = $this->getVariations(); + $productMatrix = []; + $attributes = []; + if ($variations) { + $usedProductAttributes = $this->getUsedAttributes(); + $productByUsedAttributes = $this->getAssociatedProducts(); + foreach ($variations as $variation) { + $attributeValues = []; + foreach ($usedProductAttributes as $attribute) { + $attributeValues[$attribute->getAttributeCode()] = $variation[$attribute->getId()]['value']; + } + $key = implode('-', $attributeValues); + if (isset($productByUsedAttributes[$key])) { + $product = $productByUsedAttributes[$key]; + $price = $product->getPrice(); + $variationOptions = []; + foreach ($usedProductAttributes as $attribute) { + if (!isset($attributes[$attribute->getAttributeId()])) { + $attributes[$attribute->getAttributeId()] = [ + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'id' => $attribute->getAttributeId(), + 'position' => $attribute->getPosition(), + 'chosen' => [], + ]; + foreach ($attribute->getOptions() as $option) { + if (!empty($option->getValue())) { + $attributes[$attribute->getAttributeId()]['options'][$option->getValue()] = [ + 'attribute_code' => $attribute->getAttributeCode(), + 'attribute_label' => $attribute->getStoreLabel(0), + 'id' => $option->getValue(), + 'label' => $option->getLabel(), + 'value' => $option->getValue(), + ]; + } + } + } + $optionId = $variation[$attribute->getId()]['value']; + $variationOption = [ + 'attribute_code' => $attribute->getAttributeCode(), + 'attribute_label' => $attribute->getStoreLabel(0), + 'id' => $optionId, + 'label' => $variation[$attribute->getId()]['label'], + 'value' => $optionId, + ]; + $variationOptions[] = $variationOption; + $attributes[$attribute->getAttributeId()]['chosen'][$optionId] = $variationOption; + } + + $productMatrix[] = [ + 'productId' => $product->getId(), + 'images' => [ + 'preview' => $this->image->init($product, 'product_thumbnail_image')->getUrl() + ], + 'sku' => $product->getSku(), + 'name' => $product->getName(), + 'quantity' => $this->getProductStockQty($product), + 'price' => $price, + 'options' => $variationOptions, + 'weight' => $product->getWeight(), + 'status' => $product->getStatus() + ]; + } + } + } + $this->productMatrix = $productMatrix; + $this->productAttributes = array_values($attributes); + } } diff --git a/app/code/Magento/ConfigurableProduct/Block/Product/Configurable/AssociatedSelector/Backend/Grid/ColumnSet.php b/app/code/Magento/ConfigurableProduct/Block/Product/Configurable/AssociatedSelector/Backend/Grid/ColumnSet.php deleted file mode 100644 index bf4a93a77a0890ce1305a25bb39745348b167949..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Product/Configurable/AssociatedSelector/Backend/Grid/ColumnSet.php +++ /dev/null @@ -1,109 +0,0 @@ -<?php -/** - * Block representing set of columns in product grid - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ConfigurableProduct\Block\Product\Configurable\AssociatedSelector\Backend\Grid; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ColumnSet extends \Magento\Backend\Block\Widget\Grid\ColumnSet -{ - /** - * Registry instance - * - * @var \Magento\Framework\Registry - */ - protected $_registryManager; - - /** - * Product type configurable instance - * - * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable - */ - protected $_productType; - - /** - * @param \Magento\Framework\View\Element\Template\Context $context - * @param \Magento\Backend\Model\Widget\Grid\Row\UrlGeneratorFactory $generatorFactory - * @param \Magento\Backend\Model\Widget\Grid\SubTotals $subtotals - * @param \Magento\Backend\Model\Widget\Grid\Totals $totals - * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable $productType - * @param \Magento\Framework\Registry $registryManager - * @param array $data - */ - public function __construct( - \Magento\Framework\View\Element\Template\Context $context, - \Magento\Backend\Model\Widget\Grid\Row\UrlGeneratorFactory $generatorFactory, - \Magento\Backend\Model\Widget\Grid\SubTotals $subtotals, - \Magento\Backend\Model\Widget\Grid\Totals $totals, - \Magento\ConfigurableProduct\Model\Product\Type\Configurable $productType, - \Magento\Framework\Registry $registryManager, - array $data = [] - ) { - parent::__construct($context, $generatorFactory, $subtotals, $totals, $data); - - $this->_registryManager = $registryManager; - $this->_productType = $productType; - } - - /** - * Retrieve currently edited product object - * - * @return \Magento\Catalog\Model\Product - */ - public function getProduct() - { - return $this->_registryManager->registry('current_product'); - } - - /** - * Preparing layout - * - * @return \Magento\ConfigurableProduct\Block\Product\Configurable\AssociatedSelector\Backend\Grid\ColumnSet - */ - protected function _prepareLayout() - { - parent::_prepareLayout(); - - $product = $this->getProduct(); - $attributes = $this->_productType->getUsedProductAttributes($product); - foreach ($attributes as $attribute) { - /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ - /** @var $block \Magento\Backend\Block\Widget\Grid\Column */ - $block = $this->addChild( - $attribute->getAttributeCode(), - 'Magento\Backend\Block\Widget\Grid\Column', - [ - 'header' => $attribute->getStoreLabel(), - 'index' => $attribute->getAttributeCode(), - 'type' => 'options', - 'options' => $this->getOptions($attribute->getSource()), - 'sortable' => false - ] - ); - $block->setId($attribute->getAttributeCode())->setGrid($this); - } - return $this; - } - - /** - * Get option as hash - * - * @param \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource $sourceModel - * @return array - */ - private function getOptions(\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource $sourceModel) - { - $result = []; - foreach ($sourceModel->getAllOptions() as $option) { - if ($option['value'] != '') { - $result[] = $option; - } - } - return $result; - } -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php index b67361c4fcc8a740dddceeadaed4fe4b4e3429a5..e2ba278cbc84bc83995ea7c44df653a7880b8670 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php @@ -216,9 +216,7 @@ class Configurable extends \Magento\Catalog\Block\Product\View\AbstractView { $prices = []; foreach ($this->getAllowProducts() as $product) { - $priceInfo = $this->getProduct() - ->setSelectedConfigurableOption($product) - ->getPriceInfo(); + $priceInfo = $product->getPriceInfo(); $prices[$product->getId()] = [ diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Associated/Grid.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Associated/Grid.php new file mode 100644 index 0000000000000000000000000000000000000000..6eb4facfee9c36125e9da543cfe8565732cdd9e8 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Associated/Grid.php @@ -0,0 +1,42 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Associated; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\View\Result\LayoutFactory; + +class Grid extends Action +{ + /** + * @var LayoutFactory + */ + protected $resultPageFactory; + + /** + * @param Context $context + * @param LayoutFactory $resultPageFactory + */ + public function __construct( + Context $context, + LayoutFactory $resultPageFactory + ) { + parent::__construct($context); + $this->resultPageFactory = $resultPageFactory; + } + /** + * Index action + * + * @return \Magento\Backend\Model\View\Result\Page + */ + public function execute() + { + /** @var \Magento\Framework\View\Result\Layout $resultPage */ + $resultPage = $this->resultPageFactory->create(); + return $resultPage; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/SuperConfig/Index.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/SuperConfig/Index.php deleted file mode 100644 index cd5021712d0d7411d491a77a2349c1cd0e8fa147..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/SuperConfig/Index.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\SuperConfig; - -use Magento\Backend\App\Action; -use Magento\Catalog\Controller\Adminhtml\Product; - -class Index extends Action -{ - /** - * @var \Magento\Catalog\Controller\Adminhtml\Product\Builder - */ - protected $productBuilder; - - /** - * @param Action\Context $context - * @param Product\Builder $productBuilder - */ - public function __construct(Action\Context $context, Product\Builder $productBuilder) - { - $this->productBuilder = $productBuilder; - parent::__construct($context); - } - - /** - * @return void - */ - public function execute() - { - $this->productBuilder->build($this->getRequest()); - $this->_view->loadLayout(false); - $this->_view->renderLayout(); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 4c8c10f01476eedaf6b49701fab2dd9bd4493691..46db91ca3564b3121fd87f39669eb9a4a0e7f997 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -220,7 +220,7 @@ class Configurable extends \Magento\Catalog\Model\Product\Type\AbstractType * group => array(ids) * ) * - * @param int $parentId + * @param array|int $parentId * @param bool $required * @return array */ diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php index 68c5cb91c5aa8a6ce83ba418bad122064c254459..bd74b867c86103a67177628d8bef0e30609261cb 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php @@ -24,10 +24,13 @@ class Price extends \Magento\Catalog\Model\Product\Type\Price return $product->getCalculatedFinalPrice(); } if ($product->getCustomOption('simple_product')) { - $product->setSelectedConfigurableOption($product->getCustomOption('simple_product')->getProduct()); + $simpleProduct = $product->getCustomOption('simple_product')->getProduct(); + $product->setSelectedConfigurableOption($simpleProduct); + $priceInfo = $simpleProduct->getPriceInfo(); + } else { + $priceInfo = $product->getPriceInfo(); } - //TODO: MAGETWO-23739 catalogrule price must get from simple product. - $finalPrice = $product->getPriceInfo()->getPrice('final_price')->getAmount()->getValue(); + $finalPrice = $priceInfo->getPrice('final_price')->getAmount()->getValue(); $finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice); $finalPrice = max(0, $finalPrice); $product->setFinalPrice($finalPrice); diff --git a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..3ec1ff1fcc5d008b9f2d23280627235b0f12dda7 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Model\Quote\Item; + +use Magento\Quote\Model\Quote\Item\CartItemProcessorInterface; +use Magento\Quote\Api\Data\CartItemInterface; + +class CartItemProcessor implements CartItemProcessorInterface +{ + /** + * @var \Magento\Framework\DataObject\Factory + */ + protected $objectFactory; + + /** + * @var \Magento\Quote\Model\Quote\ProductOptionFactory + */ + protected $productOptionFactory; + + /** + * @var \Magento\Quote\Api\Data\ProductOptionExtensionFactory + */ + protected $extensionFactory; + + /** + * @var \Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValueFactory + */ + protected $itemOptionValueFactory; + + /** + * @param \Magento\Framework\DataObject\Factory $objectFactory + * @param \Magento\Quote\Model\Quote\ProductOptionFactory $productOptionFactory + * @param \Magento\Quote\Api\Data\ProductOptionExtensionFactory $extensionFactory + * @param \Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValueFactory $itemOptionValueFactory + */ + public function __construct( + \Magento\Framework\DataObject\Factory $objectFactory, + \Magento\Quote\Model\Quote\ProductOptionFactory $productOptionFactory, + \Magento\Quote\Api\Data\ProductOptionExtensionFactory $extensionFactory, + \Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValueFactory $itemOptionValueFactory + ) { + $this->objectFactory = $objectFactory; + $this->productOptionFactory = $productOptionFactory; + $this->extensionFactory = $extensionFactory; + $this->itemOptionValueFactory = $itemOptionValueFactory; + } + + /** + * {@inheritdoc} + */ + public function convertToBuyRequest(CartItemInterface $cartItem) + { + if ($cartItem->getProductOption() && $cartItem->getProductOption()->getExtensionAttributes()) { + /** @var \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface $options */ + $options = $cartItem->getProductOption()->getExtensionAttributes()->getConfigurableItemOptions(); + if (is_array($options)) { + $requestData = []; + foreach ($options as $option) { + $requestData['super_attribute'][$option->getOptionId()] = $option->getOptionValue(); + } + return $this->objectFactory->create($requestData); + } + } + return null; + } + + /** + * {@inheritdoc} + */ + public function processProductOptions(CartItemInterface $cartItem) + { + $attributesOption = $cartItem->getProduct()->getCustomOption('attributes'); + $selectedConfigurableOptions = unserialize($attributesOption->getValue()); + + if (is_array($selectedConfigurableOptions)) { + $configurableOptions = []; + foreach ($selectedConfigurableOptions as $optionId => $optionValue) { + /** @var \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface $option */ + $option = $this->itemOptionValueFactory->create(); + $option->setOptionId($optionId); + $option->setOptionValue($optionValue); + $configurableOptions[] = $option; + } + + $productOption = $cartItem->getProductOption() + ? $cartItem->getProductOption() + : $this->productOptionFactory->create(); + + /** @var \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensibleAttribute */ + $extensibleAttribute = $productOption->getExtensionAttributes() + ? $productOption->getExtensionAttributes() + : $this->extensionFactory->create(); + + $extensibleAttribute->setConfigurableItemOptions($configurableOptions); + $productOption->setExtensionAttributes($extensibleAttribute); + $cartItem->setProductOption($productOption); + } + return $cartItem; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/ConfigurableItemOptionValue.php b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/ConfigurableItemOptionValue.php new file mode 100644 index 0000000000000000000000000000000000000000..b13a475b0572871fc899fea3f38b151e861e13f1 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/ConfigurableItemOptionValue.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Model\Quote\Item; + +use Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface; +use Magento\Framework\Model\AbstractExtensibleModel; + +/** + * Class ConfigurableItemOptionValue + */ +class ConfigurableItemOptionValue extends AbstractExtensibleModel implements ConfigurableItemOptionValueInterface +{ + //@codeCoverageIgnoreStart + /** + * {@inheritdoc} + */ + public function getOptionId() + { + return $this->getData(self::OPTION_ID); + } + + /** + * {@inheritdoc} + */ + public function setOptionId($value) + { + return $this->setData(self::OPTION_ID, $value); + } + + /** + * {@inheritdoc} + */ + public function getOptionValue() + { + return $this->getData(self::OPTION_VALUE); + } + + /** + * {@inheritdoc} + */ + public function setOptionValue($value) + { + return $this->setData(self::OPTION_VALUE, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * Set an extension attributes object. + * + * @param \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Collection/AssociatedProduct.php b/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Collection/AssociatedProduct.php deleted file mode 100644 index 1859c32a207bf6e1d5486e5e7f50124aeb600c1d..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Collection/AssociatedProduct.php +++ /dev/null @@ -1,184 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Catalog compare item resource model - */ -namespace Magento\ConfigurableProduct\Model\Resource\Product\Collection; - -use Magento\Customer\Api\GroupManagementInterface; - -/** - * Catalog compare item resource model - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class AssociatedProduct extends \Magento\Catalog\Model\Resource\Product\Collection -{ - /** - * Registry instance - * - * @var \Magento\Framework\Registry - */ - protected $_registryManager; - - /** - * Product type configurable instance - * - * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable - */ - protected $_productType; - - /** - * Product type configuration - * - * @var \Magento\Catalog\Model\ProductTypes\ConfigInterface - */ - protected $_productTypeConfig; - - /** - * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory - * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy - * @param \Magento\Framework\Event\ManagerInterface $eventManager - * @param \Magento\Eav\Model\Config $eavConfig - * @param \Magento\Framework\App\Resource $resource - * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory - * @param \Magento\Catalog\Model\Resource\Helper $resourceHelper - * @param \Magento\Framework\Validator\UniversalFactory $universalFactory - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\Module\Manager $moduleManager - * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory - * @param \Magento\Catalog\Model\Resource\Url $catalogUrl - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param \Magento\Customer\Model\Session $customerSession - * @param \Magento\Framework\Stdlib\DateTime $dateTime - * @param GroupManagementInterface $groupManagement - * @param \Magento\Framework\Registry $registryManager - * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable $productType - * @param \Magento\Catalog\Model\ProductTypes\ConfigInterface $productTypeConfig - * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection - * - * @SuppressWarnings(PHPMD.ExcessiveParameterList) - */ - public function __construct( - \Magento\Framework\Data\Collection\EntityFactory $entityFactory, - \Psr\Log\LoggerInterface $logger, - \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, - \Magento\Framework\Event\ManagerInterface $eventManager, - \Magento\Eav\Model\Config $eavConfig, - \Magento\Framework\App\Resource $resource, - \Magento\Eav\Model\EntityFactory $eavEntityFactory, - \Magento\Catalog\Model\Resource\Helper $resourceHelper, - \Magento\Framework\Validator\UniversalFactory $universalFactory, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Module\Manager $moduleManager, - \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory, - \Magento\Catalog\Model\Resource\Url $catalogUrl, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Customer\Model\Session $customerSession, - \Magento\Framework\Stdlib\DateTime $dateTime, - GroupManagementInterface $groupManagement, - \Magento\Framework\Registry $registryManager, - \Magento\ConfigurableProduct\Model\Product\Type\Configurable $productType, - \Magento\Catalog\Model\ProductTypes\ConfigInterface $productTypeConfig, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null - ) { - $this->_registryManager = $registryManager; - $this->_productType = $productType; - $this->_productTypeConfig = $productTypeConfig; - parent::__construct( - $entityFactory, - $logger, - $fetchStrategy, - $eventManager, - $eavConfig, - $resource, - $eavEntityFactory, - $resourceHelper, - $universalFactory, - $storeManager, - $moduleManager, - $catalogProductFlatState, - $scopeConfig, - $productOptionFactory, - $catalogUrl, - $localeDate, - $customerSession, - $dateTime, - $groupManagement, - $connection - ); - } - - /** - * Get product type - * - * @return \Magento\ConfigurableProduct\Model\Product\Type\Configurable - */ - public function getProductType() - { - return $this->_productType; - } - - /** - * Retrieve currently edited product object - * - * @return mixed - */ - private function getProduct() - { - return $this->_registryManager->registry('current_product'); - } - - /** - * Add attributes to select - * - * @return $this - */ - public function _initSelect() - { - parent::_initSelect(); - - $allowedProductTypes = $this->_productTypeConfig->getComposableTypes(); - - $this->addAttributeToSelect( - 'name' - )->addAttributeToSelect( - 'price' - )->addAttributeToSelect( - 'sku' - )->addAttributeToSelect( - 'weight' - )->addAttributeToSelect( - 'image' - )->addFieldToFilter( - 'type_id', - $allowedProductTypes - )->addFieldToFilter( - 'entity_id', - ['neq' => $this->getProduct()->getId()] - )->addFilterByRequiredOptions()->joinAttribute( - 'name', - 'catalog_product/name', - 'entity_id', - null, - 'inner' - )->joinTable( - ['cisi' => 'cataloginventory_stock_item'], - 'product_id=entity_id', - ['qty' => 'qty', 'inventory_in_stock' => 'is_in_stock'], - null, - 'left' - ); - - return $this; - } -} diff --git a/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Indexer/Price/Configurable.php index f90e8896b2551bade13425824514fcf0debef42f..1a3dd7d1e136931c0c640a43aa7b68b5c406211d 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Indexer/Price/Configurable.php @@ -135,10 +135,9 @@ class Configurable extends \Magento\Catalog\Model\Resource\Product\Indexer\Price ); $priceColumn = $this->_addAttributeToSelect($select, 'price', 'l.product_id', 0, null, true); $tierPriceColumn = $connection->getCheckSql("MIN(i.tier_price) IS NOT NULL", "i.tier_price", 'NULL'); - $groupPriceColumn = $connection->getCheckSql("MIN(i.group_price) IS NOT NULL", "i.group_price", 'NULL'); $select->columns( - ['price' => $priceColumn, 'tier_price' => $tierPriceColumn, 'group_price' => $groupPriceColumn] + ['price' => $priceColumn, 'tier_price' => $tierPriceColumn] ); $query = $select->insertFromSelect($coaTable); @@ -150,10 +149,9 @@ class Configurable extends \Magento\Catalog\Model\Resource\Product\Indexer\Price 'parent_id', 'customer_group_id', 'website_id', - $connection->getCheckSql("MIN(group_price) IS NOT NULL", "group_price", 'MIN(price)'), - $connection->getCheckSql("MIN(group_price) IS NOT NULL", "group_price", 'MAX(price)'), + 'MIN(price)', + 'MAX(price)', 'MIN(tier_price)', - 'MIN(group_price)' ] )->group( ['parent_id', 'customer_group_id', 'website_id'] @@ -174,7 +172,6 @@ class Configurable extends \Magento\Catalog\Model\Resource\Product\Indexer\Price 'min_price' => new \Zend_Db_Expr('i.min_price - i.orig_price + io.min_price'), 'max_price' => new \Zend_Db_Expr('i.max_price - i.orig_price + io.max_price'), 'tier_price' => 'io.tier_price', - 'group_price' => 'io.group_price' ] ); diff --git a/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Type/Configurable.php index 2b2d6e392bb2287f22e417e2924cafca9efd9238..c90b20f34acbb78266b5fe803a6d1c1e376994d1 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Resource/Product/Type/Configurable.php @@ -90,14 +90,13 @@ class Configurable extends \Magento\Framework\Model\Resource\Db\AbstractDb * group => array(ids) * ) * - * @param int $parentId + * @param int|array $parentId * @param bool $required * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getChildrenIds($parentId, $required = true) { - $childrenIds = []; $select = $this->getConnection()->select()->from( ['l' => $this->getMainTable()], ['product_id', 'parent_id'] @@ -106,7 +105,7 @@ class Configurable extends \Magento\Framework\Model\Resource\Db\AbstractDb 'e.entity_id = l.product_id AND e.required_options = 0', [] )->where( - 'parent_id = ?', + 'parent_id IN (?)', $parentId ); diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php index 7674b0ad7bf79a8d40bc495d95d9f56b9aaab988..76f2ef5cdd900bfe3b68950147c2f0ec988c16c8 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/BasePrice.php @@ -50,7 +50,6 @@ class BasePrice extends \Magento\Catalog\Pricing\Price\BasePrice if (null === $this->minimumAdditionalPrice) { $priceCodes = [ \Magento\Catalog\Pricing\Price\SpecialPrice::PRICE_CODE, - \Magento\Catalog\Pricing\Price\GroupPrice::PRICE_CODE, \Magento\Catalog\Pricing\Price\TierPrice::PRICE_CODE, ]; $this->minimumAdditionalPrice = false; diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..8a3a54d17f45b04fc586cca4033f13b2da176c04 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Pricing\Price; + +use Magento\Catalog\Model\Product; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Framework\Pricing\PriceCurrencyInterface; + +class ConfigurablePriceResolver implements PriceResolverInterface +{ + /** @var PriceResolverInterface */ + protected $priceResolver; + + /** @var PriceCurrencyInterface */ + protected $priceCurrency; + + /** @var Configurable */ + protected $configurable; + + /** + * @param PriceResolverInterface $priceResolver + * @param Configurable $configurable + * @param PriceCurrencyInterface $priceCurrency + */ + public function __construct( + PriceResolverInterface $priceResolver, + Configurable $configurable, + PriceCurrencyInterface $priceCurrency + ) { + $this->priceResolver = $priceResolver; + $this->configurable = $configurable; + $this->priceCurrency = $priceCurrency; + } + + /** + * @param \Magento\Framework\Pricing\Object\SaleableInterface $product + * @return float + */ + public function resolvePrice(\Magento\Framework\Pricing\Object\SaleableInterface $product) + { + $selectedConfigurableOption = $product->getSelectedConfigurableOption(); + if ($selectedConfigurableOption) { + $price = $this->priceResolver->resolvePrice($selectedConfigurableOption); + } else { + $price = null; + foreach ($this->configurable->getUsedProducts($product) as $subProduct) { + $productPrice = $this->priceResolver->resolvePrice($subProduct); + $price = $price ? min($price, $productPrice) : $productPrice; + } + } + $priceInCurrentCurrency = $this->priceCurrency->convertAndRound($price); + return $priceInCurrentCurrency ? (float)$priceInCurrentCurrency : false; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php index efb7d45698271f2ca26aa4c1161e8fbcf8958240..704005ef38b8c6aecbc18ffb797f109363907358 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php @@ -8,9 +8,7 @@ namespace Magento\ConfigurableProduct\Pricing\Price; use Magento\Catalog\Model\Product; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\Framework\Pricing\Adjustment\CalculatorInterface; use Magento\Framework\Pricing\Price\AbstractPrice; -use Magento\Framework\Pricing\PriceCurrencyInterface; /** * Class RegularPrice @@ -37,6 +35,27 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu */ protected $values = []; + /** @var PriceResolverInterface */ + protected $priceResolver; + + /** + * @param \Magento\Framework\Pricing\Object\SaleableInterface $saleableItem + * @param float $quantity + * @param \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator + * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param PriceResolverInterface $priceResolver + */ + public function __construct( + \Magento\Framework\Pricing\Object\SaleableInterface $saleableItem, + $quantity, + \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator, + \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, + PriceResolverInterface $priceResolver + ) { + parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); + $this->priceResolver = $priceResolver; + } + /** * {@inheritdoc} */ @@ -45,19 +64,7 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu $selectedConfigurableOption = $this->product->getSelectedConfigurableOption(); $productId = $selectedConfigurableOption ? $selectedConfigurableOption->getId() : $this->product->getId(); if (!isset($this->values[$productId])) { - $price = null; - if (!$selectedConfigurableOption) { - foreach ($this->getUsedProducts() as $product) { - if ($price === null || $price > $product->getPrice()) { - $price = $product->getPrice(); - } - } - } else { - $price = $selectedConfigurableOption->getPrice(); - } - - $priceInCurrentCurrency = $this->priceCurrency->convertAndRound($price); - $this->values[$productId] = $priceInCurrentCurrency ? floatval($priceInCurrentCurrency) : false; + $this->values[$productId] = $this->priceResolver->resolvePrice($this->product); } return $this->values[$productId]; @@ -67,12 +74,7 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu */ public function getAmount() { - if (false) { - // TODO: need to check simple product assignment - } else { - $amount = $this->getMinRegularAmount($this->product); - } - return $amount; + return $this->getMinRegularAmount($this->product); } /** @@ -95,7 +97,6 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu */ protected function doGetMaxRegularAmount() { - // TODO: think about quantity $maxAmount = null; foreach ($this->getUsedProducts() as $product) { $childPriceAmount = $product->getPriceInfo()->getPrice(self::PRICE_CODE)->getAmount(); @@ -125,7 +126,6 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu */ protected function doGetMinRegularAmount() { - // TODO: think about quantity $minAmount = null; foreach ($this->getUsedProducts() as $product) { $childPriceAmount = $product->getPriceInfo()->getPrice(self::PRICE_CODE)->getAmount(); diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php index 093de8327567283d0652789d9e1e499ebfd5e263..8a448ac045bd38be1151a935c8f79fcd2a6de8fe 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPrice.php @@ -8,6 +8,32 @@ namespace Magento\ConfigurableProduct\Pricing\Price; class FinalPrice extends \Magento\Catalog\Pricing\Price\FinalPrice { + /** @var PriceResolverInterface */ + protected $priceResolver; + + /** + * @var array + */ + protected $values = []; + + /** + * @param \Magento\Framework\Pricing\Object\SaleableInterface $saleableItem + * @param float $quantity + * @param \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator + * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param PriceResolverInterface $priceResolver + */ + public function __construct( + \Magento\Framework\Pricing\Object\SaleableInterface $saleableItem, + $quantity, + \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator, + \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, + PriceResolverInterface $priceResolver + ) { + parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); + $this->priceResolver = $priceResolver; + } + /** * {@inheritdoc} */ @@ -18,4 +44,18 @@ class FinalPrice extends \Magento\Catalog\Pricing\Price\FinalPrice } return parent::getAmount(); } + + /** + * {@inheritdoc} + */ + public function getValue() + { + $selectedConfigurableOption = $this->product->getSelectedConfigurableOption(); + $productId = $selectedConfigurableOption ? $selectedConfigurableOption->getId() : $this->product->getId(); + if (!isset($this->values[$productId])) { + $this->values[$productId] = $this->priceResolver->resolvePrice($this->product); + } + + return $this->values[$productId]; + } } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPriceResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..c55dd7cd1b2b56d54c56636b8c746fa8352269e6 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/FinalPriceResolver.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Pricing\Price; + +class FinalPriceResolver implements PriceResolverInterface +{ + /** + * @param \Magento\Framework\Pricing\Object\SaleableInterface $product + * @return float + */ + public function resolvePrice(\Magento\Framework\Pricing\Object\SaleableInterface $product) + { + return $product->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE) + ->getAmount()->getValue(); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/PriceResolverInterface.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/PriceResolverInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..5aff5c12e16804f8d2b967833aea7eaea24a2a16 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/PriceResolverInterface.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Pricing\Price; + +interface PriceResolverInterface +{ + /** + * @param \Magento\Framework\Pricing\Object\SaleableInterface $product + * @return float + */ + public function resolvePrice(\Magento\Framework\Pricing\Object\SaleableInterface $product); +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/RegularPriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/RegularPriceResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..a8ac2e6d84bf3ad7da02a8db8bbab683c8e3aa90 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/RegularPriceResolver.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Pricing\Price; + +class RegularPriceResolver implements PriceResolverInterface +{ + /** + * @param \Magento\Framework\Pricing\Object\SaleableInterface $product + * @return float + */ + public function resolvePrice(\Magento\Framework\Pricing\Object\SaleableInterface $product) + { + return $product->getPrice(); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Setup/InstallData.php b/app/code/Magento/ConfigurableProduct/Setup/InstallData.php index 9a886fb83ab80a089b51f789d010b2a25c856e3f..1d6705846db6ec25e9d439ddacccc0bf45e634f4 100644 --- a/app/code/Magento/ConfigurableProduct/Setup/InstallData.php +++ b/app/code/Magento/ConfigurableProduct/Setup/InstallData.php @@ -44,7 +44,6 @@ class InstallData implements InstallDataInterface $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); $attributes = [ 'country_of_manufacture', - 'group_price', 'minimal_price', 'msrp', 'msrp_display_actual_price_type', diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php index 90070991f06c59412ed9f1e9e47242de4e9b42db..844839b9b933f78e7ff4781490d117acd4474ce9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php @@ -17,12 +17,6 @@ class MatrixTest extends \PHPUnit_Framework_TestCase */ protected $_block; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_appConfig; - - /** @var \Magento\Framework\Locale\CurrencyInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_locale; - /** * @var \Magento\CatalogInventory\Api\StockRegistryInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -30,7 +24,6 @@ class MatrixTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->_appConfig = $this->getMock('Magento\Framework\App\Config\ScopeConfigInterface'); $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->stockRegistryMock = $this->getMockForAbstractClass( @@ -44,13 +37,10 @@ class MatrixTest extends \PHPUnit_Framework_TestCase ); $context = $objectHelper->getObject( - 'Magento\Backend\Block\Template\Context', - ['scopeConfig' => $this->_appConfig] + 'Magento\Backend\Block\Template\Context' ); - $this->_locale = $this->getMock('Magento\Framework\Locale\CurrencyInterface', [], [], '', false); $data = [ 'context' => $context, - 'localeCurrency' => $this->_locale, 'formFactory' => $this->getMock('Magento\Framework\Data\FormFactory', [], [], '', false), 'productFactory' => $this->getMock('Magento\Catalog\Model\ProductFactory', [], [], '', false), 'stockRegistry' => $this->stockRegistryMock, @@ -63,23 +53,6 @@ class MatrixTest extends \PHPUnit_Framework_TestCase ); } - public function testRenderPrice() - { - $this->_appConfig->expects($this->once())->method('getValue')->will($this->returnValue('USD')); - $currency = $this->getMock('Zend_Currency', [], [], '', false); - $currency->expects($this->once())->method('toCurrency')->with('100.0000')->will($this->returnValue('$100.00')); - $this->_locale->expects( - $this->once() - )->method( - 'getCurrency' - )->with( - 'USD' - )->will( - $this->returnValue($currency) - ); - $this->assertEquals('$100.00', $this->_block->renderPrice(100)); - } - /** * Run test getProductStockQty method * diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7d14671ec0d7db501a3bd58fba10ecf9d4b265d6 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Test\Unit\Model\Product\Type\Configurable; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class PriceTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price */ + protected $model; + + /** @var ObjectManagerHelper */ + protected $objectManagerHelper; + + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->model = $this->objectManagerHelper->getObject( + '\Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price' + ); + } + + public function testGetFinalPrice() + { + $finalPrice = 10; + $qty = 1; + $configurableProduct = $this->getMockBuilder('Magento\Catalog\Model\Product') + ->disableOriginalConstructor() + ->setMethods(['getCustomOption', 'setSelectedConfigurableOption', 'setFinalPrice', '__wakeUp']) + ->getMock(); + $childProduct = $this->getMockBuilder('Magento\Catalog\Model\Product') + ->disableOriginalConstructor() + ->setMethods(['getPriceInfo', '__wakeUp']) + ->getMock(); + $customOption = $this->getMockBuilder('Magento\Catalog\Model\Product\Configuration\Item\Option') + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMock(); + $priceInfo = $this->getMockBuilder('Magento\Framework\Pricing\PriceInfo\Base') + ->disableOriginalConstructor() + ->setMethods(['getPrice']) + ->getMock(); + $price = $this->getMockBuilder('Magento\Framework\Pricing\Price\PriceInterface') + ->disableOriginalConstructor() + ->getMock(); + $amount = $this->getMockBuilder('Magento\Framework\Pricing\Amount\AmountInterface') + ->disableOriginalConstructor() + ->getMock(); + + $configurableProduct->expects($this->at(0)) + ->method('getCustomOption') + ->with('simple_product') + ->willReturn($customOption); + $configurableProduct->expects($this->at(1)) + ->method('getCustomOption') + ->with('simple_product') + ->willReturn($customOption); + $customOption->expects($this->once())->method('getProduct')->willReturn($childProduct); + $configurableProduct->expects($this->once()) + ->method('setSelectedConfigurableOption') + ->with($childProduct) + ->willReturnSelf(); + $childProduct->expects($this->once())->method('getPriceInfo')->willReturn($priceInfo); + $priceInfo->expects($this->once())->method('getPrice')->with('final_price')->willReturn($price); + $price->expects($this->once())->method('getAmount')->willReturn($amount); + $amount->expects($this->once())->method('getValue')->willReturn($finalPrice); + $configurableProduct->expects($this->at(3))->method('getCustomOption')->with('option_ids')->willReturn(false); + $configurableProduct->expects($this->once())->method('setFinalPrice')->with($finalPrice)->willReturnSelf(); + + $this->assertEquals($finalPrice, $this->model->getFinalPrice($qty, $configurableProduct)); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bdaa41b5c24bbe3c390515b93deb269811b9e844 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php @@ -0,0 +1,229 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Test\Unit\Model\Quote\Item; + +class CartItemProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\ConfigurableProduct\Model\Quote\Item\CartItemProcessor + */ + protected $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $objectFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $optionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $optionExtensionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $optionValueFactoryMock; + + protected function setUp() + { + $this->objectFactoryMock = $this->getMock('\Magento\Framework\DataObject\Factory', ['create'], [], '', false); + $this->optionFactoryMock = $this->getMock( + '\Magento\Quote\Model\Quote\ProductOptionFactory', + ['create'], + [], + '', + false + ); + $this->optionExtensionFactoryMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtensionFactory', + ['create'], + [], + '', + false + ); + $this->optionValueFactoryMock = $this->getMock( + '\Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValueFactory', + ['create'], + [], + '', + false + ); + + $this->model = new \Magento\ConfigurableProduct\Model\Quote\Item\CartItemProcessor( + $this->objectFactoryMock, + $this->optionFactoryMock, + $this->optionExtensionFactoryMock, + $this->optionValueFactoryMock + ); + } + + public function testConvertToBuyRequestIfNoProductOption() + { + $cartItemMock = $this->getMock('\Magento\Quote\Api\Data\CartItemInterface'); + $cartItemMock->expects($this->once())->method('getProductOption')->willReturn(null); + $this->assertNull($this->model->convertToBuyRequest($cartItemMock)); + } + + public function testConvertToBuyRequest() + { + $optionId = 'option_id'; + $optionValue = 'option_value'; + + $productOptionMock = $this->getMock('\Magento\Quote\Api\Data\ProductOptionInterface'); + $cartItemMock = $this->getMock('\Magento\Quote\Api\Data\CartItemInterface'); + $cartItemMock->expects($this->exactly(3))->method('getProductOption')->willReturn($productOptionMock); + $extAttributesMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOption', + ['getConfigurableItemOptions'], + [], + '', + false + ); + $productOptionMock + ->expects($this->exactly(2)) + ->method('getExtensionAttributes') + ->willReturn($extAttributesMock); + + $optionValueMock = $this->getMock('\Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface'); + $extAttributesMock->expects($this->once()) + ->method('getConfigurableItemOptions') + ->willReturn([$optionValueMock]); + + $optionValueMock->expects($this->once())->method('getOptionId')->willReturn($optionId); + $optionValueMock->expects($this->once())->method('getOptionValue')->willReturn($optionValue); + + $requestData = [ + 'super_attribute' => [ + $optionId => $optionValue + ] + ]; + $buyRequestMock = new \Magento\Framework\DataObject($requestData); + $this->objectFactoryMock->expects($this->once()) + ->method('create') + ->with($requestData) + ->willReturn($buyRequestMock); + + $this->assertEquals($buyRequestMock, $this->model->convertToBuyRequest($cartItemMock)); + } + + public function testProcessProductOptionsIfOptionNotSelected() + { + $customOption = $this->getMock('\Magento\Catalog\Model\Product\Configuration\Item\Option', [], [], '', false); + $customOption->expects($this->once())->method('getValue')->willReturn(''); + + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getCustomOption')->with('attributes')->willReturn($customOption); + + $cartItemMock = $this->getMock('\Magento\Quote\Model\Quote\Item', ['getProduct'], [], '', false); + $cartItemMock->expects($this->once())->method('getProduct')->willReturn($productMock); + $this->assertEquals($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } + + public function testProcessProductOptions() + { + $optionId = 'option_id'; + $optionValue = 'option_value'; + + $customOption = $this->getMock('\Magento\Catalog\Model\Product\Configuration\Item\Option', [], [], '', false); + $customOption->expects($this->once())->method('getValue')->willReturn(serialize([$optionId => $optionValue])); + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getCustomOption')->with('attributes')->willReturn($customOption); + + $cartItemMock = $this->getMock( + '\Magento\Quote\Model\Quote\Item', + ['getProduct', 'getProductOption', 'setProductOption'], + [], + '', + false + ); + $cartItemMock->expects($this->once())->method('getProduct')->willReturn($productMock); + $cartItemMock->expects($this->once())->method('getProductOption')->willReturn(null); + + $optionValueMock = $this->getMock('\Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface'); + $this->optionValueFactoryMock->expects($this->once())->method('create')->willReturn($optionValueMock); + $optionValueMock->expects($this->once())->method('setOptionId')->with($optionId)->willReturnSelf(); + $optionValueMock->expects($this->once())->method('setOptionValue')->with($optionValue)->willReturnSelf(); + + $productOptionMock = $this->getMock('\Magento\Quote\Api\Data\ProductOptionInterface'); + $this->optionFactoryMock->expects($this->once())->method('create')->willReturn($productOptionMock); + $productOptionMock->expects($this->once())->method('getExtensionAttributes')->willReturn(null); + + $extAttributeMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtension', + ['setConfigurableItemOptions'], + [], + '', + false + ); + $this->optionExtensionFactoryMock->expects($this->once())->method('create')->willReturn($extAttributeMock); + $extAttributeMock->expects($this->once()) + ->method('setConfigurableItemOptions') + ->with([$optionValueMock]) + ->willReturnSelf(); + $productOptionMock->expects($this->once()) + ->method('setExtensionAttributes') + ->with($extAttributeMock) + ->willReturnSelf(); + $cartItemMock->expects($this->once())->method('setProductOption')->with($productOptionMock)->willReturnSelf(); + + $this->assertEquals($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } + + public function testProcessProductOptionsIfOptionsExist() + { + $optionId = 'option_id'; + $optionValue = 'option_value'; + + $customOption = $this->getMock('\Magento\Catalog\Model\Product\Configuration\Item\Option', [], [], '', false); + $customOption->expects($this->once())->method('getValue')->willReturn(serialize([$optionId => $optionValue])); + $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $productMock->expects($this->once())->method('getCustomOption')->with('attributes')->willReturn($customOption); + + $cartItemMock = $this->getMock( + '\Magento\Quote\Model\Quote\Item', + ['getProduct', 'getProductOption', 'setProductOption'], + [], + '', + false + ); + + $optionValueMock = $this->getMock('\Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface'); + $this->optionValueFactoryMock->expects($this->once())->method('create')->willReturn($optionValueMock); + $optionValueMock->expects($this->once())->method('setOptionId')->with($optionId)->willReturnSelf(); + $optionValueMock->expects($this->once())->method('setOptionValue')->with($optionValue)->willReturnSelf(); + + $extAttributeMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtension', + ['setConfigurableItemOptions'], + [], + '', + false + ); + $extAttributeMock->expects($this->once()) + ->method('setConfigurableItemOptions') + ->with([$optionValueMock]) + ->willReturnSelf(); + + $productOptionMock = $this->getMock('\Magento\Quote\Api\Data\ProductOptionInterface'); + $productOptionMock->expects($this->exactly(2))->method('getExtensionAttributes')->willReturn($extAttributeMock); + + $cartItemMock->expects($this->once())->method('getProduct')->willReturn($productMock); + $cartItemMock->expects($this->exactly(2))->method('getProductOption')->willReturn($productOptionMock); + + $productOptionMock->expects($this->once()) + ->method('setExtensionAttributes') + ->with($extAttributeMock) + ->willReturnSelf(); + $cartItemMock->expects($this->once())->method('setProductOption')->with($productOptionMock)->willReturnSelf(); + + $this->assertEquals($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Attribute/Repository.php b/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Attribute/Repository.php new file mode 100644 index 0000000000000000000000000000000000000000..d52708b06b95557c0b661aac603585a75912158e --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Attribute/Repository.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Attribute; + +class Repository extends \Magento\Catalog\Ui\Component\Listing\Attribute\AbstractRepository +{ + /** @var \Magento\Framework\App\RequestInterface */ + protected $request; + + /** + * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository + * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder + * @param \Magento\Framework\App\RequestInterface $request + */ + public function __construct( + \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository, + \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder, + \Magento\Framework\App\RequestInterface $request + ) { + parent::__construct($productAttributeRepository, $searchCriteriaBuilder); + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + protected function buildSearchCriteria() + { + return $this->searchCriteriaBuilder + ->addFilter('attribute_code', $this->request->getParam('attributes_codes', []), 'in') + ->create(); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Columns.php b/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Columns.php new file mode 100644 index 0000000000000000000000000000000000000000..90acb7e467a960fec5ea72089c4b4d2e78664a1d --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Columns.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct; + +class Columns extends \Magento\Ui\Component\Listing\Columns +{ + /** @var \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface */ + protected $attributeRepository; + + /** + * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context + * @param \Magento\Catalog\Ui\Component\ColumnFactory $columnFactory + * @param \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository + * @param array $components + * @param array $data + */ + public function __construct( + \Magento\Framework\View\Element\UiComponent\ContextInterface $context, + \Magento\Catalog\Ui\Component\ColumnFactory $columnFactory, + \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository, + array $components = [], + array $data = [] + ) { + parent::__construct($context, $components, $data); + $this->columnFactory = $columnFactory; + $this->attributeRepository = $attributeRepository; + } + + /** + * {@inheritdoc} + */ + public function prepare() + { + foreach ($this->attributeRepository->getList() as $attribute) { + $column = $this->columnFactory->create($attribute, $this->getContext()); + $column->prepare(); + $this->addComponent($attribute->getAttributeCode(), $column); + } + parent::prepare(); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Filters.php b/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Filters.php index af27ebc05c328ce4aeaf78e787fabf88abd248bf..8ed9f3d718e21f27955c53048d57721f89f61229 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Filters.php +++ b/app/code/Magento/ConfigurableProduct/Ui/Component/Listing/AssociatedProduct/Filters.php @@ -7,28 +7,26 @@ namespace Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct; class Filters extends \Magento\Ui\Component\Filters { - /** - * @var \Magento\Eav\Model\Resource\Entity\Attribute\CollectionFactory - */ - protected $attributeCollectionFactory; + /** @var \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface */ + protected $attributeRepository; /** * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context * @param \Magento\Catalog\Ui\Component\FilterFactory $filterFactory - * @param \Magento\Eav\Model\Resource\Entity\Attribute\CollectionFactory $attributeCollectionFactory + * @param \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository * @param array $components * @param array $data */ public function __construct( \Magento\Framework\View\Element\UiComponent\ContextInterface $context, \Magento\Catalog\Ui\Component\FilterFactory $filterFactory, - \Magento\Eav\Model\Resource\Entity\Attribute\CollectionFactory $attributeCollectionFactory, + \Magento\Catalog\Ui\Component\Listing\Attribute\RepositoryInterface $attributeRepository, array $components = [], array $data = [] ) { parent::__construct($context, $components, $data); $this->filterFactory = $filterFactory; - $this->attributeCollectionFactory = $attributeCollectionFactory; + $this->attributeRepository = $attributeRepository; } /** @@ -36,24 +34,14 @@ class Filters extends \Magento\Ui\Component\Filters */ public function prepare() { - $attributeIds = $this->context->getRequestParam('attribute_ids'); + $attributeIds = $this->getContext()->getRequestParam('attributes_codes'); if ($attributeIds) { - foreach ($this->getAttributes($attributeIds) as $attribute) { - $filter = $this->filterFactory->create($attribute, $this->getContext()); + foreach ($this->attributeRepository->getList() as $attribute) { + $filter = $this->filterFactory->create($attribute, $this->getContext(), ['component' => '']); $filter->prepare(); $this->addComponent($attribute->getAttributeCode(), $filter); } } parent::prepare(); } - - /** - * @param array $attributeIds - * @return mixed - */ - protected function getAttributes($attributeIds) - { - $attributeCollection = $this->attributeCollectionFactory->create(); - return $attributeCollection->addFieldToFilter('attribute_code', ['in' => $attributeIds]); - } } diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 1b6f0a5a8aea66ae6ccae51733bb3e4edac036fb..3ed8b8b4eaf5f709299df8fd1db5f517779e5b3e 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -6,13 +6,11 @@ "magento/module-store": "1.0.0-beta", "magento/module-catalog": "1.0.0-beta", "magento/module-catalog-inventory": "1.0.0-beta", - "magento/module-sales": "1.0.0-beta", "magento/module-checkout": "1.0.0-beta", "magento/module-msrp": "1.0.0-beta", "magento/module-backend": "1.0.0-beta", "magento/module-eav": "1.0.0-beta", "magento/module-customer": "1.0.0-beta", - "magento/module-directory": "1.0.0-beta", "magento/framework": "1.0.0-beta", "magento/module-media-storage": "1.0.0-beta", "magento/module-quote": "1.0.0-beta", @@ -20,7 +18,8 @@ "magento/magento-composer-installer": "*" }, "suggest": { - "magento/module-webapi": "1.0.0-beta" + "magento/module-webapi": "1.0.0-beta", + "magento/module-sales": "1.0.0-beta" }, "type": "magento2-module", "version": "1.0.0-beta", diff --git a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml index 10ffde3a203407b971fdae1ce6775763ed6464ba..226e2f9f2a2eaf852ee09b522578925dd77870c5 100644 --- a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml @@ -24,4 +24,14 @@ <type name="Magento\Catalog\Model\Resource\Product"> <plugin name="reload_attributes" type="Magento\ConfigurableProduct\Plugin\Model\Resource\Product" /> </type> + <type name="Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Columns"> + <arguments> + <argument name="attributeRepository" xsi:type="object">Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Attribute\Repository</argument> + </arguments> + </type> + <type name="Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Filters"> + <arguments> + <argument name="attributeRepository" xsi:type="object">Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Attribute\Repository</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 7f3f4dda6d5745674bc7cebb863e9fc43e2906aa..7bf23bc01780a7fda341ce3412b8b734b77a9eef 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -11,6 +11,7 @@ <preference for="Magento\ConfigurableProduct\Api\OptionRepositoryInterface" type="Magento\ConfigurableProduct\Model\OptionRepository" /> <preference for="Magento\ConfigurableProduct\Api\Data\OptionInterface" type="Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute" /> <preference for="Magento\ConfigurableProduct\Api\Data\OptionValueInterface" type="Magento\ConfigurableProduct\Model\Product\Type\Configurable\OptionValue" /> + <preference for="Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface" type="Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValue" /> <type name="Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option"> <plugin name="configurable_product" type="Magento\ConfigurableProduct\Model\Quote\Item\QuantityValidator\Initializer\Option\Plugin\ConfigurableProduct" sortOrder="50" /> </type> @@ -89,4 +90,31 @@ </argument> </arguments> </type> + <type name="Magento\Quote\Model\Quote\Item\Repository"> + <arguments> + <argument name="cartItemProcessors" xsi:type="array"> + <item name="configurable" xsi:type="object">Magento\ConfigurableProduct\Model\Quote\Item\CartItemProcessor</item> + </argument> + </arguments> + </type> + <virtualType name="ConfigurableFinalPriceResolver" type="Magento\ConfigurableProduct\Pricing\Price\ConfigurablePriceResolver"> + <arguments> + <argument name="priceResolver" xsi:type="object">Magento\ConfigurableProduct\Pricing\Price\FinalPriceResolver</argument> + </arguments> + </virtualType> + <type name="Magento\ConfigurableProduct\Pricing\Price\FinalPrice"> + <arguments> + <argument name="priceResolver" xsi:type="object">ConfigurableFinalPriceResolver</argument> + </arguments> + </type> + <virtualType name="ConfigurableRegularPriceResolver" type="Magento\ConfigurableProduct\Pricing\Price\ConfigurablePriceResolver"> + <arguments> + <argument name="priceResolver" xsi:type="object">Magento\ConfigurableProduct\Pricing\Price\RegularPriceResolver</argument> + </arguments> + </virtualType> + <type name="Magento\ConfigurableProduct\Pricing\Price\ConfigurableRegularPrice"> + <arguments> + <argument name="priceResolver" xsi:type="object">ConfigurableRegularPriceResolver</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/etc/extension_attributes.xml b/app/code/Magento/ConfigurableProduct/etc/extension_attributes.xml index 5fd3eba9ce1956d153f67551d0c0469e9018016a..255b93d18fc8329adfcf87d18760b5f284d06e74 100644 --- a/app/code/Magento/ConfigurableProduct/etc/extension_attributes.xml +++ b/app/code/Magento/ConfigurableProduct/etc/extension_attributes.xml @@ -10,4 +10,7 @@ <attribute code="configurable_product_options" type="Magento\ConfigurableProduct\Api\Data\OptionInterface[]" /> <attribute code="configurable_product_links" type="int[]" /> </extension_attributes> + <extension_attributes for="Magento\Quote\Api\Data\ProductOptionInterface"> + <attribute code="configurable_item_options" type="Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface[]" /> + </extension_attributes> </config> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_index.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_associated_grid.xml similarity index 52% rename from app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_index.xml rename to app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_associated_grid.xml index 7cc24820b426f9662631b12a351bcd0f50180b21..d9dc4c65e3331f03d50db1f22b96b3dff8a71513 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_index.xml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_associated_grid.xml @@ -6,8 +6,7 @@ */ --> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/layout_generic.xsd"> - <update handle="catalog_product_superconfig_config"/> <container name="root"> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\AssociatedProduct" name="admin.product.edit.tab.super.config.grid.container" template="Magento_ConfigurableProduct::catalog/product/edit/super/associated-product-grid.phtml"/> + <uiComponent name="configurable_associated_product_listing"/> </container> </layout> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_configurable.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_configurable.xml index 5cd281b5b426b97d7b1579170b61d74d7db59f68..2a361a45de36c62309abd33455a61e65355e376f 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_configurable.xml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_configurable.xml @@ -10,4 +10,11 @@ <css src="Magento_ConfigurableProduct::css/configurable-product.css"/> </head> <update handle="catalog_product_superconfig_config"/> + <body> + <referenceBlock name="product_tabs"> + <action method="removeTab"> + <argument name="name" xsi:type="string">advanced-pricing</argument> + </action> + </referenceBlock> + </body> </page> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_generatevariations_index.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_generatevariations_index.xml deleted file mode 100644 index 24ab7d198c0273a89aba205feb3cccb4f16b19eb..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_generatevariations_index.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/layout_generic.xsd"> - <container name="root" label="Root"> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config" template="Magento_ConfigurableProduct::catalog/product/edit/super/generator.phtml" name="product-variations-generator" as="generator"> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Attribute" template="Magento_ConfigurableProduct::catalog/product/edit/super/attribute-template.phtml" as="attribute-renderer"/> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Matrix" template="Magento_ConfigurableProduct::catalog/product/edit/super/matrix.phtml" as="matrix"/> - </block> - </container> -</layout> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_new.xml index c087f18fd3079973549dbbde3dd4d74896ea7beb..e3f54be9e1bca0ec1f190cbb21814b7728850a88 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_new.xml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_new.xml @@ -15,7 +15,9 @@ <block class="Magento\Framework\View\Element\Template" template="Magento_ConfigurableProduct::product/configurable/stock/disabler.phtml"/> </referenceContainer> <referenceContainer name="content"> - <block class="Magento\Framework\View\Element\Template" name="affected-attribute-set-form" template="Magento_ConfigurableProduct::product/configurable/affected-attribute-set-selector/form.phtml"/> + <block class="Magento\Framework\View\Element\Template" name="affected-attribute-set-selector" template="Magento_ConfigurableProduct::product/configurable/affected-attribute-set-selector/form.phtml"> + <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\ProductTemplate\Form" name="affected-attribute-set-form" /> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_config.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_config.xml index 9ed318d2b3379065b44f4f7ef78c6266ad1e6f42..bd9103028a8455ab6357816c9c474a3505127e60 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_config.xml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/layout/catalog_product_superconfig_config.xml @@ -7,24 +7,16 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd"> <body> - <referenceBlock name="admin.product.edit.tab.super.config.grid.container"> - <block class="Magento\Backend\Block\Template" name="manual.product.grid" template="Magento_ConfigurableProduct::catalog/product/edit/super/manual-product-grid.phtml" as="grid"> - <uiComponent name="configurable_associated_product_listing"/> - </block> - </referenceBlock> <referenceBlock name="product_tabs"> <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config" name="admin.product.edit.tab.super.config.grid.container"> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config" template="Magento_ConfigurableProduct::catalog/product/edit/super/generator.phtml" name="product-variations-generator" as="generator"> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Attribute" template="Magento_ConfigurableProduct::catalog/product/edit/super/attribute-template.phtml" as="attribute-renderer"/> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Matrix" template="Magento_ConfigurableProduct::catalog/product/edit/super/matrix.phtml" as="matrix"> - <block class="Magento\Ui\Block\Component\StepsWizard" name="variation-steps-wizard"> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\SelectAttributes" name="step1" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/select_attributes.phtml"> - <uiComponent name="product_attributes_listing"/> - </block> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\AttributeValues" name="step2" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/attributes_values.phtml"/> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Bulk" name="step3" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/bulk.phtml"/> - <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Summary" name="step4" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/summary.phtml"/> + <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Matrix" template="Magento_ConfigurableProduct::catalog/product/edit/super/matrix.phtml" as="matrix"> + <block class="Magento\Ui\Block\Component\StepsWizard" name="variation-steps-wizard"> + <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\SelectAttributes" name="step1" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/select_attributes.phtml"> + <uiComponent name="product_attributes_listing"/> </block> + <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\AttributeValues" name="step2" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/attributes_values.phtml"/> + <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Bulk" name="step3" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/bulk.phtml"/> + <block class="Magento\ConfigurableProduct\Block\Adminhtml\Product\Steps\Summary" name="step4" template="Magento_ConfigurableProduct::catalog/product/edit/attribute/steps/summary.phtml"/> </block> </block> </block> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml index a9add91f6760804230770254138231a6837c036c..92a4924eab2b82dda46f626952720cebe3aa75bf 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/attribute/new/created.phtml @@ -11,7 +11,7 @@ var data = <?php /* @escapeNotVerified */ echo $block->getAttributesBlockJson() ?>; var set = data.set || {id: $('#attribute_set_id').val()}; if (data.tab == 'variations') { - $('[data-role=product-variations-generator]').trigger('add', data.attribute); + $('[data-role=product-variations-matrix]').trigger('add', data.attribute); } else { $('[data-form=edit-product]').trigger('changeAttributeSet', set); } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/associated-product-grid.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/associated-product-grid.phtml deleted file mode 100644 index d6c1154113a3dfe1d072cda58436657177408191..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/associated-product-grid.phtml +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -/* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\AssociatedProduct */ - -?> -<div data-role="configurable-attribute"> - <div data-role="messages" class="messages"> - <div class="message "> - <div> - <?php if ($block->isHasRows()): ?> - <?= /* @escapeNotVerified */ __('Choose a new product to delete and replace the current product configuration.') ?> - <?php else: ?> - <?= /* @escapeNotVerified */ __('For better results, add attributes and attribute values to your products.') ?> - <?php endif ?> - </div> - </div> - </div> - <?php echo $block->getGridHtml() ?> -</div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/attribute-template.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/attribute-template.phtml deleted file mode 100644 index a8c7a24ac844bc2062e493f843044f44524e844a..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/attribute-template.phtml +++ /dev/null @@ -1,36 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -/* @var $block \Magento\Framework\View\Element\Template */ - -$id = $block->escapeHtml($attribute['attribute_id']); -?> -<div data-role="configurable-attribute"> - <input name="attributes[]" id="configurable_attribute_<?php /* @escapeNotVerified */ echo $id ?>" value="<?php /* @escapeNotVerified */ echo $id; ?>" type="hidden"/> - <input value="<?php echo $block->escapeHtml($attribute['id']); ?>" type="hidden" - name="product[configurable_attributes_data][<?php /* @escapeNotVerified */ echo $id ?>][id]"/> - <input value="<?php echo $block->escapeHtml($attribute['attribute_code']); ?>" type="hidden" - name="product[configurable_attributes_data][<?php /* @escapeNotVerified */ echo $id ?>][code]"/> - <input value="<?php /* @escapeNotVerified */ echo $id; ?>" type="hidden" - name="product[configurable_attributes_data][<?php /* @escapeNotVerified */ echo $id ?>][attribute_id]"/> - <input value="<?php echo $block->escapeHtml($attribute['position']); ?>" type="hidden" - name="product[configurable_attributes_data][<?php /* @escapeNotVerified */ echo $id ?>][position]"/> - <input type="hidden" value="<?php echo $block->escapeHtml($attribute['label']); ?>" - name="product[configurable_attributes_data][<?php /* @escapeNotVerified */ echo $id ?>][label]"/> - <input value="1" type="hidden" name="product[configurable_attributes_data][<?php /* @escapeNotVerified */ echo $id ?>][use_default]"/> - <?php - $namePrefix = 'product[configurable_attributes_data][' . $id . '][values]'; - foreach ($attribute['options'] as $option) { - $valueIndex = $option['value']; - ?> - <input type="hidden" name="<?php /* @escapeNotVerified */ echo $namePrefix ?>[<?php /* @escapeNotVerified */ echo $valueIndex ?>][value_index]" - value="<?php echo $block->escapeHtml($valueIndex); ?>"> - <input type="hidden" name="<?php /* @escapeNotVerified */ echo $namePrefix ?>[<?php /* @escapeNotVerified */ echo $valueIndex ?>][include]" value="1"/> - <?php - } ?> -</div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml index ec212367581008e27e8e3bf63107b0b67e0a9ef4..f5951e3cd280da2bd31120a001e09cb89d79acb6 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml @@ -7,128 +7,130 @@ // @codingStandardsIgnoreFile /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config */ - -use Magento\ConfigurableProduct\Model\Product\Type\Configurable; ?> -<div class="entry-edit form-inline" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>"> - <div class="fieldset-wrapper admin__collapsible-block-wrapper" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>-wrapper" data-panel="product-variations"> - <div class="fieldset-wrapper-title"> - <strong class="admin__collapsible-title" data-toggle="collapse" data-target="#<?php /* @escapeNotVerified */ echo $block->getId() ?>-content"> - <span><?= /* @escapeNotVerified */ __('Configurations') ?></span> - </strong> +<div class="entry-edit form-inline" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>" data-panel="product-variations"> + <div data-bind="scope: 'variation-steps-wizard'" class="product-create-configuration"> + <div class="product-create-configuration-info"> + <div class="note" data-role="product-create-configuration-info"> + <?= /* @escapeNotVerified */ __('Configurable products allow customers to choose options (Ex: shirt color). + You need to create a simple product for each configuration (Ex: a product for each color).');?> + </div> </div> - - <div class="fieldset-wrapper-content collapse" data-collapsed="true" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>-content"> - <div data-bind="scope: 'variation-steps-wizard'" class="product-create-configuration"> - <div class="product-create-configuration-info"> - <div class="note"> - <?= /* @escapeNotVerified */ __('Configurable products allow customers to choose options (Ex: shirt color). - You need to create a simple product for each configuration (Ex: a product for each color).');?> - </div> - </div> - <!--<div class="product-create-configuration-actions"> - <a class="action-menu-item" data-action="choose" onclick="jQuery('#associated-products-container').trigger('openModal');" href="#"> - <?/*= __('Add Products Manually');*/?> - </a> - </div>--> - <div class="product-create-configuration-actions"> - <button data-action="open-steps-wizard" title="Create Product Configurations" - class="action-secondary" data-bind="click: open"> - <span data-role="button-label" data-edit-label="<?= /* @escapeNotVerified */ __('Edit Configurations') ?>"> - <?= /* @escapeNotVerified */ $block->getProduct()->getId() - && $block->getProduct()->getTypeId() === 'configurable' - ? __('Edit Configurations') - : __('Create Configurations') - ?> - </span> - </button> - </div> + <div class="product-create-configuration-actions"> + <div class="product-create-configuration-action"> + <button data-action="open-steps-wizard" title="Create Product Configurations" + class="action-secondary" data-bind="click: open"> + <span data-role="button-label" data-edit-label="<?= /* @escapeNotVerified */ __('Edit Configurations') ?>"> + <?= /* @escapeNotVerified */ $block->getProduct()->getId() && $block->isConfigurableProduct() + ? __('Edit Configurations') + : __('Create Configurations') + ?> + </span> + </button> + </div> + <div class="product-create-configuration-action" data-bind="scope: 'configurableProductGrid'"> + <button class="action-tertiary action-menu-item" type="button" data-action="choose" + data-bind="click: showManuallyGrid, visible: button"> + <?= /* @noEscape */ __('Add Products Manually');?> + </button> </div> - <fieldset class="fieldset"> - <?php echo $block->getChildHtml('generator'); ?> + </div> + </div> + <fieldset class="fieldset"> + <?php echo $block->getChildHtml('matrix'); ?> - <!-- Select Associated Product popup --> - <div id="associated-products-container"> - <?php echo $block->getGridHtml(); ?> + <!-- Select Associated Product popup --> + <div data-grid-id="associated-products-container"> + <div class="admin__data-grid-outer-wrap" data-bind="scope: 'configurable_associated_product_listing.configurable_associated_product_listing'"> + <div data-role="spinner" data-component="configurable_associated_product_listing.configurable_associated_product_listing.product_columns" class="admin__data-grid-loading-mask"> + <div class="spinner"> + <span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span> + </div> </div> + <!-- ko template: getTemplate() --><!-- /ko --> - <input type="hidden" name="affect_configurable_product_attributes" value="1" /> - </fieldset> + </div> </div> - </div> + + <input type="hidden" name="affect_configurable_product_attributes" value="1" /> + </fieldset> </div> <script> require([ 'jquery', - 'mage/template', - 'jquery/ui', - 'jquery/jquery.tabs', - 'mage/mage', - 'mage/backend/menu', + 'Magento_ConfigurableProduct/js/advanced-pricing-handler', + 'Magento_ConfigurableProduct/js/options/price-type-handler', + 'collapsible', 'Magento_Ui/js/modal/modal', 'mage/translate', 'domReady!' -], function($, mageTemplate){ +], function($, advancedPricingHandler, priceTypeHandler){ 'use strict'; - var variationsContainer = $("#<?php /* @escapeNotVerified */ echo $block->getId()?>"), - collapsableWrapper = $("#<?php /* @escapeNotVerified */ echo $block->getId()?>-wrapper"), - collapsableArea = $('> .collapse', collapsableWrapper), - stockAvailabilityField = $('#quantity_and_stock_status'), - qtyField = $('#qty'), - inventoryQty = $('#inventory_qty'), - inventoryAvailabilityField = $('#inventory_stock_availability'), - currentProductTemplateControl = $('#product-template-suggest-container .action-dropdown > .action-toggle'), - attributesInput = $("input[name='attributes[]']", variationsContainer), - hasVariations = <?php /* @escapeNotVerified */ echo($block->getProduct()->getTypeId() === Configurable::TYPE_CODE || $block->getRequest()->has('attributes')) ? 'true' : 'false' ?>, - noImageUrl = '<?= /* @escapeNotVerified */ $block->getNoImageUrl() ?>', - isLocked = function (element) { - return element.is('[data-locked]'); - }, - setElementDisabled = function (element, state, triggerEvent) { - if (!isLocked(element)) { - element.prop('disabled', state); + advancedPricingHandler.init(); + priceTypeHandler.init(); + + var blockId = '#<?= /* @noEscape */ $block->getId()?>', + hasVariations = <?= /* @noEscape */ $block->isConfigurableProduct() ? 'true' : 'false' ?>, + setElementDisabled = function ($element, state, triggerEvent) { + if (!$element.is('[data-locked]')) { + $element.prop('disabled', state); + if (triggerEvent) { - element.trigger('change') + $element.trigger('change'); } } + }, + configurableTypeHandler = function (isConfigurable) { + isConfigurable = hasVariations ? true : isConfigurable; + $('[data-form=edit-product]').trigger("change_configurable_type", isConfigurable); }; - collapsableArea - .on('show', function() { - currentProductTemplateControl - .addClass('disabled') - .prop('disabled', true); - - attributesInput.prop('disabled', false); - - inventoryQty.prop('disabled', true); - inventoryAvailabilityField.removeProp('disabled'); - setElementDisabled(qtyField, true, true); - setElementDisabled(stockAvailabilityField, false, false); - }) - .on('hide', function() { - currentProductTemplateControl - .removeClass('disabled') - .removeProp('disabled'); - inventoryQty.removeProp('disabled'); - inventoryAvailabilityField.prop('disabled', true); - setElementDisabled(stockAvailabilityField, true, false); - setElementDisabled(qtyField, false, true); - }) - .collapse(hasVariations ? 'show' : 'hide'); - - var $gridDialog = $('#associated-products-container').modal({ - title: $.mage.__('Select Associated Product'), - type: 'popup' - }); - - $('[data-panel=product-variations]') - .on('disable', '[data-role=product-variations-matrix]', function() { - $(this).html(''); + $('[data-tab=<?= /* @noEscape */ $block->getTabId()?>]') + .on('dimensionsChanged', function (event, data) { + var suggestContainer = $('#product-template-suggest-container .action-dropdown > .action-toggle'); + if (data.opened) { + suggestContainer + .addClass('disabled') + .prop('disabled', true); + $(blockId + ' input[name="attributes[]"]').prop('disabled', false); + $('#inventory_qty').prop('disabled', true); + $('#inventory_stock_availability').removeProp('disabled'); + setElementDisabled($('#qty'), true, true); + setElementDisabled($('#quantity_and_stock_status'), false, false); + configurableTypeHandler(true); + } else { + suggestContainer + .removeClass('disabled') + .removeProp('disabled'); + $('#inventory_qty').removeProp('disabled'); + $('#inventory_stock_availability').prop('disabled', true); + setElementDisabled($('#quantity_and_stock_status'), true, false); + setElementDisabled($('#qty'), false, true); + configurableTypeHandler(false); + } }) - .on('click', '[data-action=choose]', function(event) { - $gridDialog.trigger('openModal'); + .on('collapsiblecreated', function() { + $(this).collapsible(hasVariations ? 'activate' : 'deactivate'); }); }); </script> +<script type="text/x-magento-init"> + { + "*": { + "Magento_Ui/js/core/app": { + "components": { + "configurableProductGrid": { + "component": "Magento_ConfigurableProduct/js/variations/product-grid", + "productsFilter": "configurable_associated_product_listing.configurable_associated_product_listing.listing_top.listing_filters", + "productsProvider": "configurable_associated_product_listing.data_source", + "productsMassAction": "configurable_associated_product_listing.configurable_associated_product_listing.product_columns.ids", + "productsColumns": "configurable_associated_product_listing.configurable_associated_product_listing.product_columns", + "productsGridUrl": "<?= /* @noEscape */ $block->getUrl('catalog/product/associated_grid', ['componentJson' => true])?>", + "configurableVariations": "configurableVariations" + } + } + } + } + } +</script> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/generator.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/generator.phtml deleted file mode 100644 index bf2f9ac3f78109ccdd018c8cacb6e094ee87f250..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/generator.phtml +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -/** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config */ -?> -<div data-role="product-variations-generator"> - <!-- Variations --> - <div id="product-variations-matrix" data-role="product-variations-matrix"> - <?php echo $block->getChildHtml('matrix'); ?> - </div> -</div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/manual-product-grid.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/manual-product-grid.phtml deleted file mode 100644 index 41c67a6f96a3e3bb04eb2e5fdaa56919808c77cf..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/manual-product-grid.phtml +++ /dev/null @@ -1,22 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -/* @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\ManualProduct */ - -?> -<div> - <div data-role="messages" class="messages"> - <div class="message "> - <div> - <?= /* @escapeNotVerified */ __('Choose a new product to delete and replace the current product configuration.') ?> - <?= /* @escapeNotVerified */ __('For better results, add attributes and attribute values to your products.') ?> - </div> - </div> - </div> - <?= $block->getChildHtml(); ?> -</div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml index 9d66be34c0d79a94e889a3d506c88d95e1c502bd..e962963ccac6ca28245e4e25edc5b0917c01bfd9 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/matrix.phtml @@ -9,310 +9,242 @@ /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Matrix */ ?> <?php -$variations = $block->getVariations(); -$productMatrix = []; -$attributes = []; -if ($variations) { - $usedProductAttributes = $block->getUsedAttributes(); - $productByUsedAttributes = $block->getAssociatedProducts(); - $productPrice = (float)$block->getProduct()->getPrice(); - foreach ($variations as $variation) { - $attributeValues = []; - $price = $productPrice; - foreach ($usedProductAttributes as $attribute) { - $attributeValues[$attribute->getAttributeCode()] = $variation[$attribute->getId()]['value']; - } - $key = implode('-', $attributeValues); - if (isset($productByUsedAttributes[$key])) { - $product = $productByUsedAttributes[$key]; - $price = $originPrice = $product->getPrice(); - $variationOptions = []; - foreach ($usedProductAttributes as $attribute) { - if (!isset($attributes[$attribute->getAttributeId()])) { - $attributes[$attribute->getAttributeId()] = [ - 'code' => $attribute->getAttributeCode(), - 'label' => $attribute->getStoreLabel(), - 'id' => $attribute->getAttributeId(), - 'position' => $attribute->getPosition(), - 'chosen' => [], - ]; - } - $optionId = $variation[$attribute->getId()]['value']; - $variationOption = [ - 'attribute_code' => $attribute->getAttributeCode(), - 'attribute_label' => $attribute->getStoreLabel(0), - 'id' => $optionId, - 'label' => $variation[$attribute->getId()]['label'], - 'value' => $optionId, - ]; - $variationOptions[] = $variationOption; - $attributes[$attribute->getAttributeId()]['chosen'][$optionId] = $variationOption; - } - - $productMatrix[] = [ - 'productId' => $product->getId(), - 'images' => [ - 'preview' => $this->helper('Magento\Catalog\Helper\Image') - ->init($product, 'product_thumbnail_image') - ->getUrl() - ], - 'sku' => $product->getSku(), - 'name' => $product->getName(), - 'quantity' => $block->getProductStockQty($product), - 'price' => $price, - 'options' => $variationOptions, - 'weight' => $product->getWeight(), - 'status' => $product->getStatus() - ]; - } - } - $attributes = array_values($attributes); -} +$productMatrix = $block->getProductMatrix(); +$attributes = $block->getProductAttributes(); +$currencySymbol = $block->getCurrencySymbol(); ?> -<div data-bind="scope: 'configurableVariations'"> - <h3 class="hidden" data-bind="css: {hidden: !showVariations() }" class="title"> - <?php /* @escapeNotVerified */ echo __('Current Variations'); ?> - </h3> +<div id="product-variations-matrix" data-role="product-variations-matrix"> + <div data-bind="scope: 'configurableVariations'"> + <h3 class="hidden" data-bind="css: {hidden: !showVariations() }" class="title"> + <?php /* @escapeNotVerified */ echo __('Current Variations'); ?> + </h3> - <script data-template-for="variation-image" type="text/x-magento-template"> - <img src="<%= data.url %>" class="variation" data-role="image"/> - </script> - <!-- ko if: showVariations() --> - <div class="field hidden" data-bind="css: {hidden: !showVariations() }"> - <div data-role="configurable-attributes-container"> - <!-- ko foreach: {data: attributes, as: 'attribute'} --> - <div data-role="attribute-info"> - <input name="attributes[]" - data-bind="value: attribute.id, attr:{id: 'configurable_attribute_' + attribute.id}" - type="hidden"/> - <input data-bind="value: attribute.id, - attr: {name: $parent.getAttributeRowName(attribute, 'attribute_id')}" - type="hidden"/> - <input data-bind="value: attribute.code, - attr: {name: $parent.getAttributeRowName(attribute, 'code')}" - type="hidden"/> - <input - data-bind="value: attribute.label, - attr: {name: $parent.getAttributeRowName(attribute, 'label')}" - type="hidden"/> - <input data-bind="value: $index(), - attr: {name: $parent.getAttributeRowName(attribute, 'position')}" - type="hidden"/> - <!-- ko foreach: {data: attribute.chosen, as: 'option'} --> - <div data-role="option-info"> - <input value="1" - data-bind="attr: {name: $parents[1].getOptionRowName(attribute, option, 'include')}" - type="hidden"/> - <input data-bind="value: option.value, - attr: {name: $parents[1].getOptionRowName(attribute, option, 'value_index')}" - type="hidden"/> - </div> - <!-- /ko --> - </div> - <!-- /ko --> - </div> - <div class="admin__data-grid-wrap admin__data-grid-wrap-static"> - <table class="data-grid"> - <thead> - <tr> - <th class="data-grid-th data-grid-thumbnail-cell col-image" data-column="image"> - <?= /* @escapeNotVerified */ __('Image'); ?> - </th> - <th class="data-grid-th col-name" data-column="name"> - <?= /* @escapeNotVerified */ __('Name'); ?> - </th> - <th class="data-grid-th col-sku" data-column="sku"> - <?= /* @escapeNotVerified */ __('SKU'); ?> - </th> - <th class="data-grid-th col-price" data-column="price"> - <?= /* @escapeNotVerified */ __('Price'); ?> - </th> - <th class="data-grid-th col-qty" data-column="qty"> - <?= /* @escapeNotVerified */ __('Quantity'); ?> - </th> - <th class="data-grid-th col-weight" data-column="weight"> - <?= /* @escapeNotVerified */ __('Weight'); ?> - </th> - <!-- ko foreach: getAttributesOptions() --> - <th data-bind="attr: {class:'data-grid-th col-' + $data.attribute_code}, - text: $data.attribute_label"> - </th> + <script data-template-for="variation-image" type="text/x-magento-template"> + <img src="<%= data.url %>" class="variation" data-role="image"/> + </script> + <!-- ko if: showVariations() --> + <div class="field hidden" data-bind="css: {hidden: !showVariations() }"> + <div data-role="configurable-attributes-container"> + <!-- ko foreach: {data: attributes, as: 'attribute'} --> + <div data-role="attribute-info"> + <input name="attributes[]" data-bind="value: attribute.id, attr:{id: 'configurable_attribute_' + attribute.id}" type="hidden"/> + <input data-bind="value: attribute.id, attr: {name: $parent.getAttributeRowName(attribute, 'attribute_id')}" type="hidden"/> + <input data-bind="value: attribute.code, attr: {name: $parent.getAttributeRowName(attribute, 'code')}" type="hidden"/> + <input data-bind="value: attribute.label, attr: {name: $parent.getAttributeRowName(attribute, 'label')}" type="hidden"/> + <input data-bind="value: $index(), attr: {name: $parent.getAttributeRowName(attribute, 'position')}" type="hidden"/> + <!-- ko foreach: {data: attribute.chosen, as: 'option'} --> + <div data-role="option-info"> + <input value="1" data-bind="attr: {name: $parents[1].getOptionRowName(attribute, option, 'include')}" type="hidden"/> + <input data-bind="value: option.value, attr: {name: $parents[1].getOptionRowName(attribute, option, 'value_index')}" type="hidden"/> + </div> <!-- /ko --> - <th class="data-grid-th"> - <?= /* @escapeNotVerified */ __('Actions'); ?> - </th> - </tr> - </thead> - <tbody data-bind="foreach: {data: productMatrix, as: 'variation'}"> - <tr data-role="row" data-bind="css: { _disabled: !variation.status, even: $index() % 2 }, - attr: { 'data-row-number': $index() }"> - <td class="col-image" data-column="image"> - <div class="actions split actions-image-uploader"> - <div class="action-upload action split" data-action="upload-image"> - <!-- ko if: $parent.isShowPreviewImage(variation) --> - <img data-bind='attr: {src: variation.images.preview}' class="variation" - data-role="image"/> - <!-- /ko --> - <!-- ko if: variation.editable--> - <input type="hidden" data-bind=" - attr: {id: $parent.getRowId(variation, 'image'), - name: $parent.getVariationRowName(variation, 'image')}"/> - <span><?= /* @escapeNotVerified */ __('Upload Image'); ?></span> - <input name="image" type="file" - data-url="<?= $block->escapeHtml($block->getImageUploadUrl()) ?>" - title="<?= $block->escapeHtml(__('Upload image')); ?>"/> + </div> + <!-- /ko --> + </div> + <div class="admin__data-grid-wrap admin__data-grid-wrap-static"> + <table class="data-grid"> + <thead> + <tr> + <th class="data-grid-th data-grid-thumbnail-cell col-image" data-column="image"> + <?= /* @escapeNotVerified */ __('Image'); ?> + </th> + <th class="data-grid-th col-name" data-column="name"> + <?= /* @escapeNotVerified */ __('Name'); ?> + </th> + <th class="data-grid-th col-sku" data-column="sku"> + <?= /* @escapeNotVerified */ __('SKU'); ?> + </th> + <th class="data-grid-th col-price" data-column="price"> + <?= /* @escapeNotVerified */ __('Price'); ?> + </th> + <th class="data-grid-th col-qty" data-column="qty"> + <?= /* @escapeNotVerified */ __('Quantity'); ?> + </th> + <th class="data-grid-th col-weight" data-column="weight"> + <?= /* @escapeNotVerified */ __('Weight'); ?> + </th> + <!-- ko foreach: getAttributesOptions() --> + <th data-bind="attr: {class:'data-grid-th col-' + $data.attribute_code}, + text: $data.attribute_label"> + </th> + <!-- /ko --> + <th class="data-grid-th"> + <?= /* @escapeNotVerified */ __('Actions'); ?> + </th> + </tr> + </thead> + <tbody data-bind="foreach: {data: productMatrix, as: 'variation'}"> + <tr data-role="row" data-bind="css: { _disabled: !variation.status, even: $index() % 2 }, + attr: { 'data-row-number': $index() }"> + <td class="col-image" data-column="image"> + <div class="actions split actions-image-uploader"> + <div class="action-upload action split" data-action="upload-image"> + <!-- ko if: $parent.isShowPreviewImage(variation) --> + <img data-bind='attr: {src: variation.images.preview}' class="variation" + data-role="image"/> + <!-- /ko --> + <!-- ko if: variation.editable--> + <input type="hidden" data-bind=" + attr: {id: $parent.getRowId(variation, 'image'), + name: $parent.getVariationRowName(variation, 'image')}"/> + <span><?= /* @escapeNotVerified */ __('Upload Image'); ?></span> + <input name="image" type="file" + data-url="<?= $block->escapeHtml($block->getImageUploadUrl()) ?>" + title="<?= $block->escapeHtml(__('Upload image')); ?>"/> + <!-- /ko --> + </div> + <!-- ko if: variation.editable --> + <!-- ko if: variation.images.file --> + <span data-bind="html: $parent.generateImageGallery(variation)" + data-block="variation-images"></span> + <!-- /ko --> <!-- /ko --> + <button type="button" class="action toggle no-display" data-toggle="dropdown" + data-mage-init='{"dropdown":{}}'> + <span><?= /* @escapeNotVerified */ __('Select'); ?></span> + </button> + <ul class="dropdown"> + <li> + <a class="item" data-action="no-image"><?= /* @escapeNotVerified */ __('No Image'); ?></a> + </li> + </ul> </div> + </td> + <td class="col-name" data-column="name"> + <!-- ko ifnot: variation.editable --> + <a target="_blank" data-bind="text: variation.name, + attr: {href: variation.productUrl}"> + </a> + <!-- /ko --> <!-- ko if: variation.editable --> - <!-- ko if: variation.images.file --> - <span data-bind="html: $parent.generateImageGallery(variation)" - data-block="variation-images"></span> - <!-- /ko --> + <input type="text" class="required-entry" data-bind=" + attr: {id: $parent.getRowId(variation, 'name'), + name: $parent.getVariationRowName(variation, 'name'), + value: variation.sku}"/> + <input type="hidden" data-bind=" + attr: {id: $parent.getRowId(variation, 'configurable_attribute'), + name: $parent.getVariationRowName(variation, 'configurable_attribute'), + value: variation.attribute}"/> <!-- /ko --> - <button type="button" class="action toggle no-display" data-toggle="dropdown" - data-mage-init='{"dropdown":{}}'> - <span><?= /* @escapeNotVerified */ __('Select'); ?></span> - </button> - <ul class="dropdown"> - <li> - <a class="item" data-action="no-image"><?= /* @escapeNotVerified */ __('No Image'); ?></a> - </li> - </ul> - </div> - </td> - <td class="col-name" data-column="name"> - <!-- ko ifnot: variation.editable --> - <a target="_blank" data-bind="text: variation.name, - attr: {href: variation.productUrl}"> - </a> - <!-- /ko --> - <!-- ko if: variation.editable --> - <input type="text" class="required-entry" data-bind=" - attr: {id: $parent.getRowId(variation, 'name'), - name: $parent.getVariationRowName(variation, 'name'), - value: variation.sku}"/> - <input type="hidden" data-bind=" - attr: {id: $parent.getRowId(variation, 'configurable_attribute'), - name: $parent.getVariationRowName(variation, 'configurable_attribute'), - value: variation.attribute}"/> - <!-- /ko --> - <input type="hidden" data-role="status" data-bind=" - attr: {name: $parent.getVariationRowName(variation, 'status'), - value: variation.status}"/> - </td> - <td class="col-sku" data-column="sku"> - <!-- ko ifnot: variation.editable --> - <!-- ko text: variation.sku --><!--/ko--> - <!-- /ko --> - <!-- ko if: variation.editable --> - <input type="text" class="required-entry" - data-bind="attr: {id: $parent.getRowId(variation, 'sku'), - name: $parent.getVariationRowName(variation, 'sku'), - value: variation.sku}"/> - <!-- /ko --> - </td> - <td class="col-price" data-column="price"> - <!-- ko ifnot: variation.editable --> - <!-- ko text: variation.price --><!--/ko--> - <!-- /ko --> - <!-- ko if: variation.editable --> - <div class="addon"> + <input type="hidden" data-role="status" data-bind=" + attr: {name: $parent.getVariationRowName(variation, 'status'), + value: variation.status}"/> + </td> + <td class="col-sku" data-column="sku"> + <!-- ko ifnot: variation.editable --> + <!-- ko text: variation.sku --><!--/ko--> + <!-- /ko --> + <!-- ko if: variation.editable --> + <input type="text" class="required-entry" + data-bind="attr: {id: $parent.getRowId(variation, 'sku'), + name: $parent.getVariationRowName(variation, 'sku'), + value: variation.sku}"/> + <!-- /ko --> + </td> + <td class="col-price" data-column="price"> + <!-- ko ifnot: variation.editable --> + <!-- ko text: '<?= /* @noEscape */ $currencySymbol?>' + variation.price --><!--/ko--> + <!-- /ko --> + <!-- ko if: variation.editable --> + <div class="addon"> + <input type="text" + id="" + class="required-entry validate-zero-or-greater" + data-bind="attr: {id: $parent.getRowId(variation, 'price'), + name: $parent.getVariationRowName(variation, 'price'), + value: variation.price}"/> + <label class="addafter" + data-bind="attr: {for: $parent.getRowId(variation, 'price')"> + <strong>$</strong> + </label> + </div> + <!-- /ko --> + </td> + <td class="col-qty" data-column="qty"> + <!-- ko ifnot: variation.editable --> + <!-- ko text: variation.quantity --><!--/ko--> + <!-- /ko --> + <!-- ko if: variation.editable --> <input type="text" - id="" - class="required-entry validate-zero-or-greater" - data-bind="attr: {id: $parent.getRowId(variation, 'price'), - name: $parent.getVariationRowName(variation, 'price'), - value: variation.price}"/> - <label class="addafter" - data-bind="attr: {for: $parent.getRowId(variation, 'price')"> - <strong>$</strong> - </label> - </div> - <!-- /ko --> - </td> - <td class="col-qty" data-column="qty"> - <!-- ko ifnot: variation.editable --> - <!-- ko text: variation.quantity --><!--/ko--> - <!-- /ko --> - <!-- ko if: variation.editable --> - <input type="text" - class="validate-zero-or-greater" - data-bind="attr: {id: $parent.getRowId(variation, 'qty'), - name: $parent.getVariationRowName(variation, 'quantity_and_stock_status/qty'), - value: variation.quantity}"/> - <!-- /ko --> - </td> - <td class="col-weight" data-column="weight"> - <!-- ko ifnot: variation.editable --> - <!-- ko text: variation.weight --><!--/ko--> - <!-- /ko --> - <!-- ko if: variation.editable --> - <input type="text" class="validate-zero-or-greater" - data-bind=" - attr: {id: $parent.getRowId(variation, 'weight'), - name: $parent.getVariationRowName(variation, 'weight'), - value: variation.weight}"/> - <!-- /ko --> - </td> - <!-- ko foreach: variation.options --> - <td data-bind="text: label"></td> - <!-- /ko --> - <td class="data-grid-actions-cell"> - <input type="hidden" name="associated_product_ids[]" data-bind="value: variation.productId" data-column="entity_id"/> - <div class="action-select-wrap" data-bind=" - css : { - '_active' : $parent.opened() === $index() - }, - outerClick: $parent.closeList.bind($parent, $index)" - > - <button class="action-select" data-bind="click: $parent.toggleList.bind($parent, $index())"> - <span data-bind="i18n: 'Select'"></span> - </button> - <ul class="action-menu _active" data-bind="css: {'_active': $parent.opened() === $index()}"> - <li> - <a class="action-menu-item" + class="validate-zero-or-greater" + data-bind="attr: {id: $parent.getRowId(variation, 'qty'), + name: $parent.getVariationRowName(variation, 'quantity_and_stock_status/qty'), + value: variation.quantity}"/> + <!-- /ko --> + </td> + <td class="col-weight" data-column="weight"> + <!-- ko ifnot: variation.editable --> + <!-- ko text: variation.weight --><!--/ko--> + <!-- /ko --> + <!-- ko if: variation.editable --> + <input type="text" class="validate-zero-or-greater" data-bind=" - attr: {'data-attributes': variation.attribute}, - click: $parent.showGrid.bind($parent, $index()) - " - data-action="choose" - href="#"> - <?= /* @escapeNotVerified */ __('Choose a different Product');?> - </a> - </li> - <li> - <a class="action-menu-item" data-bind=" - i18n: variation.status == 1 ? 'Disable Product' : 'Enable Product', - click: $parent.toggleProduct.bind($parent, $index())"> - </a> - </li> - <li> - <a class="action-menu-item" data-bind="click: $parent.removeProduct.bind($parent, $index())"> - <?= /* @escapeNotVerified */ __('Remove Product');?> - </a> - </li> - </ul> - </div> - </td> - </tr> - </tbody> - </table> + attr: {id: $parent.getRowId(variation, 'weight'), + name: $parent.getVariationRowName(variation, 'weight'), + value: variation.weight}"/> + <!-- /ko --> + </td> + <!-- ko foreach: variation.options --> + <td data-bind="text: label"></td> + <!-- /ko --> + <td class="data-grid-actions-cell"> + <input type="hidden" name="associated_product_ids[]" data-bind="value: variation.productId" data-column="entity_id"/> + <div class="action-select-wrap" data-bind=" + css : { + '_active' : $parent.opened() === $index() + }, + outerClick: $parent.closeList.bind($parent, $index)" + > + <button class="action-select" data-bind="click: $parent.toggleList.bind($parent, $index())"> + <span data-bind="i18n: 'Select'"></span> + </button> + <ul class="action-menu _active" data-bind="css: {'_active': $parent.opened() === $index()}"> + <li> + <a class="action-menu-item" + data-bind=" + attr: {'data-attributes': variation.attribute}, + click: $parent.showGrid.bind($parent, $index()) + " + data-action="choose" + href="#"> + <?= /* @escapeNotVerified */ __('Choose a different Product');?> + </a> + </li> + <li> + <a class="action-menu-item" data-bind=" + i18n: variation.status == 1 ? 'Disable Product' : 'Enable Product', + click: $parent.toggleProduct.bind($parent, $index())"> + </a> + </li> + <li> + <a class="action-menu-item" data-bind="click: $parent.removeProduct.bind($parent, $index())"> + <?= /* @escapeNotVerified */ __('Remove Product');?> + </a> + </li> + </ul> + </div> + </td> + </tr> + </tbody> + </table> + </div> </div> - </div> - <!-- /ko --> -</div> -<div data-role="step-wizard-dialog" - data-mage-init='{"Magento_Ui/js/modal/modal":{"type":"slide","title":"<?= /* @escapeNotVerified */ __('Create Product Configurations') ?>", - "buttons":[]}}' - class="no-display"> - <?php - /* @escapeNotVerified */ echo $block->getVariationWizard([ - 'attributes' => $attributes, - 'configurations' => $productMatrix - ]); - ?> + <!-- /ko --> + </div> + <div data-role="step-wizard-dialog" + data-mage-init='{"Magento_Ui/js/modal/modal":{"type":"slide","title":"<?= /* @escapeNotVerified */ __('Create Product Configurations') ?>", + "buttons":[]}}' + class="no-display"> + <?php + /* @escapeNotVerified */ echo $block->getVariationWizard([ + 'attributes' => $attributes, + 'configurations' => $productMatrix + ]); + ?> + </div> </div> + <script type="text/x-magento-init"> { "*": { @@ -320,16 +252,13 @@ if ($variations) { "components": { "configurableVariations": { "component": "Magento_ConfigurableProduct/js/variations/variations", - "variations": <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($productMatrix) ?>, - "productAttributes": <?= /* @escapeNotVerified */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($attributes) ?>, - "productUrl": "<?= /* @escapeNotVerified */ $block->getUrl('catalog/product/edit', ['id' => '%id%']) ?>", - "associatedProductsFilter": "configurable_associated_product_listing.configurable_associated_product_listing.listing_top.listing_filters", - "associatedProductsProvider": "configurable_associated_product_listing.data_source" - + "variations": <?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($productMatrix) ?>, + "productAttributes": <?= /* @noEscape */ $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($attributes) ?>, + "productUrl": "<?= /* @noEscape */ $block->getUrl('catalog/product/edit', ['id' => '%id%']) ?>", + "configurableProductGrid": "configurableProductGrid" } } } } } - </script> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml index 61cd9a7741f61b35db4b9dae2da23ad5f6b05682..fdfe0f0077cc3c3392c36d80865e115c4ffa1763 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/form.phtml @@ -8,56 +8,6 @@ /* @var $block \Magento\Framework\View\Element\Template */ ?> -<div id="<?php /* @escapeNotVerified */ echo $block->getNameInLayout() ?>" style="display: none" data-role="dialog" data-id="affected-attribute-set-selector"> - <form action="" class="affected-attribute-set"> - <div class="messages"> - <div class="message message-notice notice"> - <div> - <?php /* @escapeNotVerified */ echo __("Product template comprising all selected configurable attributes need to be in order to save generated variations.") ?> - </div> - </div> - <div class="message message-error error" style="display: none"></div> - </div> - <fieldset class="admin__fieldset"> - <div class="admin__field" id="affected-attribute-set-current-container" data-role="container" data-id="current-attribute-set"> - <div class="admin__field admin__field-option"> - <input type="radio" - id="affected-attribute-set-current" - name="affected-attribute-set" - value="current" - checked="checked" - class="admin__control-radio"/> - <label class="admin__field-label" for="affected-attribute-set-current"> - <span><?php /* @escapeNotVerified */ echo __('Add configurable attributes to the current set ("<span data-role="name-container">%1</span>")', $block->getLayout()->getBlock('product_edit')->getAttributeSetName()) ?></span> - </label> - </div> - </div> - - <div class="admin__field admin__field-option" id="affected-attribute-set-new-container"> - <div class="control"> - <input type="radio" - id="affected-attribute-set-new" - name="affected-attribute-set" - value="new" - class="admin__control-radio"/> - <label class="admin__field-label" for="affected-attribute-set-new"> - <span><?php /* @escapeNotVerified */ echo __('Add configurable attributes to the new set based on current')?></span> - </label> - </div> - </div> - - <div class="admin__field _required" id="affected-attribute-set-new-name-container" style="display: none"> - <label class="admin__field-label" for="new-attribute-set-name"> - <span><?php /* @escapeNotVerified */ echo __('New product template name')?></span> - </label> - - <div class="admin__field-control"> - <input id="new-attribute-set-name" - name="new-attribute-set-name" - type="text" - class="admin__control-text required-entry validate-no-html-tags"/> - </div> - </div> - </fieldset> - </form> +<div data-role="affected-attribute-set-selector" class="no-display affected-attribute-set"> + <?= $block->getChildHtml('affected-attribute-set-form'); ?> </div> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml index 354305ae89dd1a543a91e7c97c0fb8ff4bd98480..111ab842f22a9143806406e44411b7cea0584d78 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/affected-attribute-set-selector/js.phtml @@ -15,55 +15,49 @@ var $form; require([ - "jquery", - "jquery/ui" + 'jquery', + 'jquery/ui', + 'Magento_Ui/js/modal/modal' ], function($){ - $form = $('#affected-attribute-set-form'); - + $form = $('[data-role=affected-attribute-set-selector]'); var resetValidation = function() { - $form.find('.messages .message.error').hide(); - $form.find('form').validation().data('validator').resetForm(); - }; + $form.find('.messages .message.error').hide(); + $form.find('form').validation().data('validator').resetForm(); + }, + setAttributeSetId = function (id) { + $('[data-role=new-variations-attribute-set-id]').val(id); + }, + closeDialogAndProcessForm = function (form) { + form.modal('closeModal').data('target').click(); + }, + newAttributeSetContainer = $('[data-role=affected-attribute-set-new-name-container]'), + existingAttributeSetContainer = $('[data-role=affected-attribute-set-existing-name-container]'); + $('[data-form=edit-product]').append($('<input>', { type: 'hidden', name: 'new-variations-attribute-set-id', - id: 'new-variations-attribute-set-id' + 'data-role': 'new-variations-attribute-set-id' })); $form - .dialog({ + .modal({ title: '<?php /* @escapeNotVerified */ echo __('Choose Affected Product Template'); ?>', - autoOpen: false, - id: '<?php /* @escapeNotVerified */ echo $block->getJsId() ?>', - width: '75%', - modal: true, - resizable: false, - position: { - my: 'left top', - at: 'center top', - of: 'body' - }, - open: function () { - $(this).closest('.ui-dialog').addClass('ui-dialog-active'); - - var topMargin = $(this).closest('.ui-dialog').children('.ui-dialog-titlebar').outerHeight(); - $(this).closest('.ui-dialog').css('margin-top', topMargin); - }, - close: function () { + closed: function () { resetValidation(); - $(this).closest('.ui-dialog').removeClass('ui-dialog-active'); }, buttons: [{ text: '<?php /* @escapeNotVerified */ echo __('Confirm'); ?>', - id: '<?php /* @escapeNotVerified */ echo $block->getJsId('confirm-button') ?>', - 'class': 'action-primary', + 'class': 'action-secondary', click: function() { - if ($form.find('input[name=affected-attribute-set]:checked').val() == 'current') { - $('#new-variations-attribute-set-id').val($('#attribute_set_id').val()); - $form.dialog('close') - .data('target').click(); + var affectedAttributeSetId = $form.find('input[name=affected-attribute-set]:checked').val(); + if (affectedAttributeSetId == 'current') { + setAttributeSetId($('#attribute_set_id').val()); + closeDialogAndProcessForm($form); return; + } else if (affectedAttributeSetId == 'existing') { + setAttributeSetId($('select', existingAttributeSetContainer).val()); + closeDialogAndProcessForm($form); } $form.find('.messages .message.error').hide(); @@ -88,9 +82,8 @@ }) .success(function (data) { if (!data.error) { - $('#new-variations-attribute-set-id').val(data.id); - $form.dialog('close') - .data('target').click(); + setAttributeSetId(data.id); + closeDialogAndProcessForm($form); } else { $form.find('.messages .message.error').replaceWith($(data.messages).find('.message.error')); } @@ -100,18 +93,19 @@ } },{ text: '<?php /* @escapeNotVerified */ echo __('Cancel'); ?>', - id: '<?php /* @escapeNotVerified */ echo $block->getJsId('close-button') ?>', 'class': 'action-close', click: function() { - $form.dialog('close'); + $form.modal('closeModal'); } }] }) .find('input[name=affected-attribute-set]').on('change', function() { - $('#affected-attribute-set-new-name-container')[$(this).val() == 'new' ? 'show' : 'hide'](); + var affectedAttributeSet = $(this).val(); + newAttributeSetContainer[affectedAttributeSet == 'new' ? 'show' : 'hide'](); + existingAttributeSetContainer[affectedAttributeSet == 'existing' ? 'show' : 'hide'](); resetValidation(); - if ($(this).val() == 'new') { - $form.find('input[name=new-attribute-set-name]').focus(); + if (affectedAttributeSet == 'new') { + newAttributeSetContainer.find('input[name=new-attribute-set-name]').focus(); } }); }); @@ -145,7 +139,7 @@ * This fix was made in order to properly handle 'stopImmediatePropagation'. */ $('#save-split-button .item').on('click', function(event) { - if ($('#new-variations-attribute-set-id').val() != '') { + if ($('[data-role=new-variations-attribute-set-id]').val() != '') { return; // affected attribute set was already chosen } @@ -157,7 +151,7 @@ } }); if (!extendingAttributes.length) { - $('#new-variations-attribute-set-id').val($('#attribute_set_id').val()); + $('[data-role=new-variations-attribute-set-id]').val($('#attribute_set_id').val()); return; // all selected configurable attributes belong to current attribute set } if (!$('[data-role=product-variations-matrix] [data-column=entity_id]') @@ -168,7 +162,7 @@ event.stopImmediatePropagation(); - $form.data('target', event.target).dialog('open'); + $form.data('target', event.target).modal('openModal'); }); }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/stock/disabler.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/stock/disabler.phtml index 266b3b34cdf35c71f64340cac96f834ad69d38b0..80abd76ba00b21f9d14c39017acf10b12c12fc88 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/stock/disabler.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/product/configurable/stock/disabler.phtml @@ -8,7 +8,7 @@ require(['jquery'], function($){ $('[data-tab-panel=product-details]').on('stockbeforedisable', function(e) { var variations = $('[data-panel=product-variations]'); - if (e.productType === 'configurable' || (variations.is(':visible') && variations.is('.opened'))) { + if (e.productType === 'configurable' || (variations.is(':visible'))) { e.stopPropagation(); return false; } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml index aee041d2b5bb8e7e6198b807e51738f5c9f67e81..f8ff2b7872c7cac2898d4e1baa2c12bc03520db4 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml @@ -40,43 +40,33 @@ <filters name="listing_filters" class="Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Filters"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> - <item name="links" xsi:type="array"> - <item name="applied" xsi:type="string">not-save-filter</item> + <item name="params" xsi:type="array"> + <item name="filters_modifier" xsi:type="array" /> </item> <item name="displayArea" xsi:type="string">dataGridFilters</item> <item name="dataScope" xsi:type="string">filters</item> - <item name="storageConfig" xsi:type="array"> - <item name="provider" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.listing_top.bookmarks</item> - <item name="namespace" xsi:type="string">current.filters</item> - </item> <item name="childDefaults" xsi:type="array"> <item name="provider" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.listing_top.listing_filters</item> - <item name="imports" xsi:type="array"> - <item name="visible" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.listing_top.bookmarks:current.columns.${ $.index }.visible</item> - </item> </item> </item> </argument> - <filterInput name="name"> + <filterInput name="entity_id"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> - <item name="dataScope" xsi:type="string">name</item> + <item name="component" xsi:type="string" /> + <item name="dataScope" xsi:type="string">entity_id</item> <item name="label" xsi:type="string" translate="true">Name</item> </item> </argument> </filterInput> - <filterSelect name="attribute_set_id"> - <argument name="optionsProvider" xsi:type="configurableObject"> - <argument name="class" xsi:type="string">Magento\Catalog\Model\Product\AttributeSet\Options</argument> - </argument> + <filterInput name="name"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> - <item name="dataScope" xsi:type="string">attribute_set_id</item> - <item name="caption" xsi:type="string" translate="true">Select...</item> - <item name="label" xsi:type="string" translate="true">Attribute Set</item> + <item name="dataScope" xsi:type="string">name</item> + <item name="label" xsi:type="string" translate="true">Name</item> </item> </argument> - </filterSelect> + </filterInput> <filterInput name="sku"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -131,10 +121,6 @@ <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="selectProvider" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.product_columns.ids</item> - <item name="storageConfig" xsi:type="array"> - <item name="provider" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.listing_top.bookmarks</item> - <item name="namespace" xsi:type="string">current.paging</item> - </item> <item name="displayArea" xsi:type="string">bottom</item> <item name="options" xsi:type="array"> <item name="20" xsi:type="array"> @@ -162,27 +148,18 @@ </argument> </paging> </container> - <columns name="product_columns" class="Magento\Catalog\Ui\Component\Listing\Columns"> + <columns name="product_columns" class="Magento\ConfigurableProduct\Ui\Component\Listing\AssociatedProduct\Columns"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> - <item name="storageConfig" xsi:type="array"> - <item name="provider" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.listing_top.bookmarks</item> - <item name="namespace" xsi:type="string">current</item> - </item> <item name="childDefaults" xsi:type="array"> <item name="fieldAction" xsi:type="array"> - <item name="provider" xsi:type="string">configurableVariations</item> + <item name="provider" xsi:type="string">configurableProductGrid</item> <item name="target" xsi:type="string">selectProduct</item> <item name="params" xsi:type="array"> <item name="0" xsi:type="string">${ $.$data.rowIndex }</item> </item> </item> <item name="controlVisibility" xsi:type="boolean">true</item> - <item name="storageConfig" xsi:type="array"> - <item name="provider" xsi:type="string">configurable_associated_product_listing.configurable_associated_product_listing.listing_top.bookmarks</item> - <item name="root" xsi:type="string">columns.${ $.index }</item> - <item name="namespace" xsi:type="string">current.${ $.storageConfig.root}</item> - </item> </item> </item> </argument> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js new file mode 100644 index 0000000000000000000000000000000000000000..3943ac39574018ce7b10a6b4c4912d99b540e434 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js @@ -0,0 +1,31 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + return { + init: function () { + $('[data-form=edit-product]') + .on('change_configurable_type', function (event, isConfigurable) { + var toggleDisabledAttribute = function (disabled) { + $('[data-tab-panel=advanced-pricing]').find('input,select').each( + function (event, element) { + $(element).attr('disabled', disabled); + } + ); + }; + if (isConfigurable) { + $('[data-ui-id=product-tabs-tab-link-advanced-pricing]').hide(); + } else { + $('[data-ui-id=product-tabs-tab-link-advanced-pricing]').show(); + } + toggleDisabledAttribute(isConfigurable); + }); + } + }; +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js new file mode 100644 index 0000000000000000000000000000000000000000..fbee8e2b47873b78e6d2cc6d4c8acab5fefe2e26 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js @@ -0,0 +1,79 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'notification', + 'mage/translate' +], function ($) { + 'use strict'; + + return { + isConfigurable: false, + messageInited: false, + messageSelector: '[data-role=product-custom-options-content]', + isPercentPriceTypeExist: function () { + var productOptionsContainer = $('#product_options_container_top'); + + return !!productOptionsContainer.length; + }, + showWarning: function () { + if (!this.messageInited) { + $(this.messageSelector).notification(); + this.messageInited = true; + } + this.hideWarning(); + $(this.messageSelector).notification('add', { + message: $.mage.__('We can\'t save custom-defined options with price type "percent" for ' + + 'configurable product.'), + error: true, + messageContainer: this.messageSelector + }); + }, + hideWarning: function () { + $(this.messageSelector).notification('clear'); + }, + init: function () { + $('[data-form=edit-product]') + .on('change_configurable_type', function (event, isConfigurable) { + this.isConfigurable = isConfigurable; + if (this.isPercentPriceTypeExist()) { + this.percentPriceTypeHandler(); + } + }.bind(this)); + + $('#product-edit-form-tabs').on('change', '.opt-type > select', function () { + var selected = $('.opt-type > select :selected'), + optGroup = selected.parent().attr('label'); + + if (optGroup === 'Select') { + $('#product-edit-form-tabs').on( + 'click', + '[data-ui-id="admin-product-options-options-box-select-option-type-add-select-row-button"]', + function () { + this.percentPriceTypeHandler(); + }.bind(this) + ); + } else { + this.percentPriceTypeHandler(); + } + }.bind(this)); + }, + percentPriceTypeHandler: function () { + var priceType = $('[data-attr="price-type"]'), + optionPercentPriceType = priceType.find('option[value="percent"]'); + + if (this.isConfigurable) { + this.showWarning(); + optionPercentPriceType.hide(); + optionPercentPriceType.parent().val() === 'percent' ? optionPercentPriceType.parent().val('fixed') : ''; + } else { + $(this.messageSelector).notification(); + optionPercentPriceType.show(); + this.hideWarning(); + } + } + }; +}); \ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/product-grid.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/product-grid.js new file mode 100644 index 0000000000000000000000000000000000000000..39267f93a149ee3247773d529e7ac8cd293e2860 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/product-grid.js @@ -0,0 +1,344 @@ +// jscs:disable requireDotNotation +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'uiComponent', + 'jquery', + 'Magento_Ui/js/core/app', + 'underscore', + 'notification' +], function (Component, $, bootstrap, _) { + 'use strict'; + + return Component.extend({ + defaults: { + productsGridUrl: null, + productAttributes: [], + productsModal: null, + button: '', + gridSelector: '[data-grid-id=associated-products-container]', + modules: { + productsFilter: '${ $.productsFilter }', + productsProvider: '${ $.productsProvider }', + productsMassAction: '${ $.productsMassAction }', + productsColumns: '${ $.productsColumns }', + variationsComponent: '${ $.configurableVariations }' + }, + listens: { + '${ $.productsProvider }:data': '_showMessageAssociatedGrid _handleManualGridOpening', + '${ $.productsMassAction }:selected': '_handleManualGridSelect', + '${ $.configurableVariations }:productMatrix': '_showButtonAddManual' + } + }, + + /** + * Initialize + * @param {Array} options + */ + initialize: function (options) { + this._super(options); + this.productsModal = $(this.gridSelector).modal({ + title: $.mage.__('Select Associated Product'), + type: 'slide', + buttons: [ + { + text: $.mage.__('Cancel'), + + /** + * Close modal + * @event + */ + click: function () { + this.closeModal(); + } + }, { + text: $.mage.__('Done'), + click: this.close.bind(this, null) + } + ] + }); + + this.productsProvider(function () { + this.productsModal.notification(); + }.bind(this)); + this.variationsComponent(function (variation) { + this._showButtonAddManual(variation.productMatrix()); + }.bind(this)); + + this._initGrid = _.once(this._initGrid); + }, + + /** + * Initial observerable + * @returns {*} + */ + initObservable: function () { + this._super().observe('button'); + + return this; + }, + + /** + * init Grid + * @private + */ + _initGrid: function (filterData) { + $.ajax({ + type: 'GET', + url: this._buildGridUrl(filterData), + context: $('body') + }).success(function (data) { + bootstrap(JSON.parse(data)); + }); + }, + + /** + * Select different product in configurations section + * @see configurable_associated_product_listing.xml + * @param {Integer} rowIndex + */ + selectProduct: function (rowIndex) { + this.close(rowIndex); + }, + + /** + * Open + * @param {Object} filterData - filter data + * @param {Object|*} filterData.filters - attribute name + * @param {Object|*} filterData.filters_modifier - modifier value + * @param {String} callbackName + * @param {Boolean} showMassActionColumn + */ + open: function (filterData, callbackName, showMassActionColumn) { + this.callbackName = callbackName; + this.productsMassAction(function (massActionComponent) { + this.productsColumns().elems().each(function (rowElement) { + rowElement.disableAction(showMassActionColumn); + }); + massActionComponent.visible(showMassActionColumn); + }.bind(this)); + this._setFilter(filterData); + this._initGrid(filterData); + this.productsModal.trigger('openModal'); + }, + + /** + * Close + */ + close: function (rowIndex) { + try { + if (this.productsMassAction().selected().length) { + this.variationsComponent()[this.callbackName](this.productsMassAction() + .selected.map(this.getProductById.bind(this))); + this.productsMassAction().selected([]); + } else if (!_.isNull(rowIndex)) { + this.variationsComponent()[this.callbackName]([this.getProductByIndex(rowIndex)]); + } + this.productsModal.trigger('closeModal'); + } catch (e) { + if (e.name === 'UserException') { + this.productsModal.notification('clear'); + this.productsModal.notification('add', { + message: e.message, + messageContainer: this.gridSelector + }); + } else { + throw e; + } + } + }, + + /** + * Get product by id + * @param {Integer} productId + * @returns {*} + */ + getProductById: function (productId) { + return _.findWhere(this.productsProvider().data.items, { + 'entity_id': productId + }); + }, + + /** + * Get product + * @param {Integer} rowIndex + * @returns {*} + */ + getProductByIndex: function (rowIndex) { + return this.productsProvider().data.items[rowIndex]; + }, + + /** + * Build grid url + * @private + */ + _buildGridUrl: function (filterData) { + var params = '?' + $.param({ + 'filters': filterData.filters, + 'attributes_codes': this._getAttributesCodes(), + 'filters_modifier': filterData['filters_modifier'] + }); + + return this.productsGridUrl + params; + }, + + /** + * Show button add manual + * @param {Array} variations + * @returns {*} + * @private + */ + _showButtonAddManual: function (variations) { + return this.button(variations.length); + }, + + /** + * Get attributes codes used for configurable + * @private + */ + _getAttributesCodes: function () { + return this.variationsComponent().attributes.pluck('code'); + }, + + /** + * Show data associated grid + * @private + */ + _showMessageAssociatedGrid: function (data) { + this.productsModal.notification('clear'); + + if (data.items.length) { + this.productsModal.notification('add', { + message: $.mage.__( + 'Choose a new product to delete and replace the current product configuration.' + ), + messageContainer: this.gridSelector + }); + } else { + this.productsModal.notification('add', { + message: $.mage.__('For better results, add attributes and attribute values to your products.'), + messageContainer: this.gridSelector + }); + } + }, + + /** + * Show manually grid + */ + showManuallyGrid: function () { + var filterModifier = _.mapObject(_.object(this._getAttributesCodes(), []), function () { + return { + 'condition_type': 'notnull' + }; + }), + usedProductIds = _.values(this.variationsComponent().productAttributesMap); + + if (usedProductIds) { + filterModifier['entity_id'] = { + 'condition_type': 'nin', value: usedProductIds + }; + } + + this.open( + { + 'filters_modifier': filterModifier + }, + 'appendProducts', + true + ); + }, + + /** + * Handle manual grid after opening + * @private + */ + _handleManualGridOpening: function (data) { + if (data.items.length && this.callbackName == 'appendProducts') { + this.productsColumns().elems().each(function (rowElement) { + rowElement.disableAction(true); + }); + + this._disableRows(data.items); + } + }, + + /** + * Disable rows in grid for products with the same variation key + * + * @param {Array} items + * @param {Array} selectedVariationKeys + * @param {Array} selected + * @private + */ + _disableRows: function (items, selectedVariationKeys, selected) { + selectedVariationKeys = selectedVariationKeys === undefined ? [] : selectedVariationKeys; + selected = selected === undefined ? [] : selected; + this.productsMassAction(function (massaction) { + var configurableVariationKeys = _.union( + selectedVariationKeys, + _.pluck(this.variationsComponent().productMatrix(), 'variationKey') + ), + variationKeyMap = this._getVariationKeyMap(items), + rowsForDisable = _.keys(_.pick( + variationKeyMap, + function (variationKey) { + return configurableVariationKeys.indexOf(variationKey) != -1; + } + )); + + massaction.disabled(_.difference(rowsForDisable, selected)); + }.bind(this)); + }, + + /** + * @private + */ + _handleManualGridSelect: function (selected) { + if (this.callbackName == 'appendProducts') { + var selectedRows = _.filter(this.productsProvider().data.items, function (row) { + return selected.indexOf(row['entity_id']) != -1; + }), + selectedVariationKeys = _.values(this._getVariationKeyMap(selectedRows)); + this._disableRows(this.productsProvider().data.items, selectedVariationKeys, selected); + } + }, + + /** + * Get variation key map used in manual grid. + * + * @param items + * @returns {Array} [{entity_id: variation-key}, ...] + * @private + */ + _getVariationKeyMap: function (items) { + this._variationKeyMap = {}; + _.each(items, function (row) { + this._variationKeyMap[row['entity_id']] = _.values( + _.pick(row, this._getAttributesCodes()) + ).sort().join('-'); + + }.bind(this)); + return this._variationKeyMap; + }, + + /** + * Set filter + * @private + */ + _setFilter: function (filterData) { + this.productsProvider(function (provider) { + provider.params['filters_modifier'] = filterData['filters_modifier']; + provider.params['attributes_codes'] = this._getAttributesCodes(); + }.bind(this)); + + this.productsFilter(function (filter) { + filter.set('filters', _.extend({ + 'filters_modifier': filterData['filters_modifier'] + }, filterData.filters)) + .apply(); + }); + } + }); +}); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js index fd928ca0f777904475b554dc3923e7eff93a1ee0..78eac86cb658f129ff6836571a5e19c5eff6ec42 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js @@ -2,6 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ +// jscs:disable jsDoc define([ 'uiComponent', 'jquery', @@ -15,16 +16,17 @@ define([ //connect items with observableArrays ko.bindingHandlers.sortableList = { - init: function(element, valueAccessor) { + init: function (element, valueAccessor) { var list = valueAccessor(); $(element).sortable({ axis: 'y', handle: '[data-role="draggable"]', tolerance: 'pointer', - update: function(event, ui) { - var item = ko.contextFor(ui.item[0]).$data; - var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]); + update: function (event, ui) { + var item = ko.contextFor(ui.item[0]).$data, + position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]); + if (ko.contextFor(ui.item[0]).$index() != position) { if (position >= 0) { list.remove(item); @@ -38,25 +40,35 @@ define([ }; return Collapsible.extend({ - stepInitialized: false, - attributes: ko.observableArray([]), defaults: { notificationMessage: { text: null, error: null - } + }, + createOptionsUrl: null, + attributes: [], + stepInitialized: false }, initialize: function () { this._super(); - this.createAttribute = _.wrap(this.createAttribute.bind(this), function () { - var args = Array.prototype.slice.call(arguments); - return this.doInitSavedOptions.call(this, args.shift().apply(this, args)); + this.createAttribute = _.wrap(this.createAttribute, function () { + var args = _.toArray(arguments), + createAttribute = args.shift(); + + return this.doInitSavedOptions(createAttribute.apply(this, args)); }); this.createAttribute = _.memoize(this.createAttribute.bind(this), _.property('id')); }, + initObservable: function () { + this._super().observe(['attributes']); + + return this; + }, createOption: function () { // this - current attribute - this.options.push({value: 0, label: '', id: utils.uniqueid(), attribute_id: this.id, is_new: true}); + this.options.push({ + value: 0, label: '', id: utils.uniqueid(), attribute_id: this.id, is_new: true + }); }, saveOption: function (option) { if (!_.isEmpty(option.label)) { @@ -78,6 +90,7 @@ define([ attribute.chosenOptions = ko.observableArray([]); attribute.options = ko.observableArray(_.map(attribute.options, function (option) { option.id = utils.uniqueid(); + return option; })); attribute.opened = ko.observable(this.initialOpened(index)); @@ -91,15 +104,19 @@ define([ }, saveAttribute: function () { var errorMessage = $.mage.__('Select options for all attributes or remove unused attributes.'); - this.attributes.each(function(attribute) { + this.attributes.each(function (attribute) { attribute.chosen = []; + if (!attribute.chosenOptions.getLength()) { throw new Error(errorMessage); } - attribute.chosenOptions.each(function(id) { - attribute.chosen.push(attribute.options.findWhere({id:id})); + attribute.chosenOptions.each(function (id) { + attribute.chosen.push(attribute.options.findWhere({ + id: id + })); }); }); + if (!this.attributes().length) { throw new Error(errorMessage); } @@ -110,30 +127,37 @@ define([ deSelectAllAttributes: function (attribute) { attribute.chosenOptions.removeAll(); }, - saveOptions: function() { + saveOptions: function () { var options = []; - this.attributes.each(function(attribute) { - attribute.chosenOptions.each(function(id) { - var option = attribute.options.findWhere({id:id, is_new: true}); + this.attributes.each(function (attribute) { + attribute.chosenOptions.each(function (id) { + var option = attribute.options.findWhere({ + id: id, is_new: true + }); if (option) { options.push(option); } }); }); + if (!options.length) { return false; } $.ajax({ - type: "POST", + type: 'POST', url: this.createOptionsUrl, - data: {options: options}, + data: { + options: options + }, showLoader: true - }).done(function(options) { - this.attributes.each(function(attribute) { - _.each(options, function(newOptionId, oldOptionId) { - var option = attribute.options.findWhere({id:oldOptionId}); + }).done(function (savedOptions) { + this.attributes.each(function (attribute) { + _.each(savedOptions, function (newOptionId, oldOptionId) { + var option = attribute.options.findWhere({ + id: oldOptionId + }); if (option) { attribute.options.remove(option); @@ -148,41 +172,46 @@ define([ }, requestAttributes: function (attributeIds) { $.ajax({ - type: "POST", + type: 'POST', url: this.optionsUrl, - data: {attributes: attributeIds}, + data: { + attributes: attributeIds + }, showLoader: true - }).done(function(attributes){ - attributes = _.sortBy(attributes, function(attribute) { + }).done(function (attributes) { + attributes = _.sortBy(attributes, function (attribute) { return this.wizard.data.attributesIds.indexOf(attribute.id); }.bind(this)); this.attributes(_.map(attributes, this.createAttribute)); }.bind(this)); }, - doInitSavedOptions: function(attribute) { - var selectedAttribute = _.findWhere(this.initData.attributes, {id: attribute.id}); + doInitSavedOptions: function (attribute) { + var selectedOptions, selectedOptionsIds, selectedAttribute = _.findWhere(this.initData.attributes, { + id: attribute.id + }); if (selectedAttribute) { - var selectedOptions = _.pluck(selectedAttribute.chosen, 'value'); - var selectedOptionsIds = _.pluck(_.filter(attribute.options(), function (option) { + selectedOptions = _.pluck(selectedAttribute.chosen, 'value'); + selectedOptionsIds = _.pluck(_.filter(attribute.options(), function (option) { return _.contains(selectedOptions, option.value); }), 'id'); attribute.chosenOptions(selectedOptionsIds); this.initData.attributes = _.without(this.initData.attributes, selectedAttribute); } + return attribute; }, - render: function(wizard) { + render: function (wizard) { this.wizard = wizard; this.requestAttributes(wizard.data.attributesIds()); }, - force: function(wizard) { + force: function (wizard) { this.saveOptions(); this.saveAttribute(wizard); wizard.data.attributes = this.attributes; }, - back: function(wizard) { + back: function (wizard) { wizard.data.attributesIds(this.attributes().pluck('id')); } }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js index bf58fc27d47a4c8a17698fc1177688392edbf124..435a7ace21418d35de1bf96cf4b24b490315258b 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js @@ -4,6 +4,7 @@ */ /*jshint browser:true jquery:true*/ /*global FORM_KEY*/ +// jscs:disable jsDoc define([ 'uiComponent', 'jquery', @@ -25,8 +26,8 @@ define([ attributes: [], sections: {}, images: null, - price: "", - quantity: "", + price: '', + quantity: '', notificationMessage: { text: null, error: null @@ -34,6 +35,7 @@ define([ }, initObservable: function () { this._super().observe('countVariations attributes sections'); + return this; }, initialize: function () { @@ -67,13 +69,15 @@ define([ this.quantity = self.quantity; }; this.makeImages = function (images, typePreview) { + var preview; + if (!images) { this.images = []; this.preview = self.noImage; this.file = null; } else { this.images = images; - var preview = _.find(this.images, function (image) { + preview = _.find(this.images, function (image) { return _.contains(image.galleryTypes, typePreview); }); @@ -87,25 +91,27 @@ define([ } }; this.images = new this.makeImages(); - _.each(this.sections(), function(section) { - section.type.subscribe(function(newValue) { - this.setWizardNotifyMessageDependOnSectionType() + _.each(this.sections(), function (section) { + section.type.subscribe(function () { + this.setWizardNotifyMessageDependOnSectionType(); }.bind(this)); }, this); }, types: ['each', 'single', 'none'], - setWizardNotifyMessageDependOnSectionType: function() { + setWizardNotifyMessageDependOnSectionType: function () { var flag = false; - _.each(this.sections(), function(section) { + _.each(this.sections(), function (section) { if (section.type() !== 'none') { flag = true; } }, this); if (flag) { - this.wizard.setNotificationMessage($.mage.__('Choose this option to delete and replace extension data '+ - 'for all past configurations.')); + this.wizard.setNotificationMessage( + $.mage.__('Choose this option to delete and replace extension data ' + + 'for all past configurations.') + ); } else { this.wizard.cleanNotificationMessage(); } @@ -113,7 +119,8 @@ define([ render: function (wizard) { this.wizard = wizard; this.attributes(wizard.data.attributes()); - if (this.mode == 'edit') { + + if (this.mode === 'edit') { this.setWizardNotifyMessageDependOnSectionType(); } //fill option section data @@ -130,35 +137,39 @@ define([ this.initCountVariations(); this.bindGalleries(); }, - initCountVariations: function() { - var variations = this.generateVariation(this.attributes()); - var newVariations = _.map(variations, function(options) { - return this.variationsComponent().getVariationKey(options) - }.bind(this)); - var existingVariations = _.keys(this.variationsComponent().productAttributesMap); + initCountVariations: function () { + var variations = this.generateVariation(this.attributes()), + newVariations = _.map(variations, function (options) { + return this.variationsComponent().getVariationKey(options); + }.bind(this)), + existingVariations = _.keys(this.variationsComponent().productAttributesMap); this.countVariations(_.difference(newVariations, existingVariations).length); - } , + }, + /** * @param attributes example [['b1', 'b2'],['a1', 'a2', 'a3'],['c1', 'c2', 'c3'],['d1']] * @returns {*} example [['b1','a1','c1','d1'],['b1','a1','c2','d1']...] */ generateVariation: function (attributes) { - return _.reduce(attributes, function(matrix, attribute) { + return _.reduce(attributes, function (matrix, attribute) { var tmp = []; - _.each(matrix, function(variations){ - _.each(attribute.chosen, function(option){ + _.each(matrix, function (variations) { + _.each(attribute.chosen, function (option) { option.attribute_code = attribute.code; option.attribute_label = attribute.label; tmp.push(_.union(variations, [option])); }); }); + if (!tmp.length) { - return _.map(attribute.chosen, function(option){ + return _.map(attribute.chosen, function (option) { option.attribute_code = attribute.code; option.attribute_label = attribute.label; + return [option]; }); } + return tmp; }, []); }, @@ -170,18 +181,20 @@ define([ return chosen.id == option.id; }); }).sections()[section]; + case 'single': return this.sections()[section].value(); + case 'none': return this[section]; } }, getImageProperty: function (node) { - var types = node.find('[data-role=gallery]').productGallery('option').types; - var images = _.map(node.find('[data-role=image]'), function (image) { + var types = node.find('[data-role=gallery]').productGallery('option').types, + images = _.map(node.find('[data-role=image]'), function (image) { var imageData = $(image).data('imageData'); imageData.galleryTypes = _.pluck(_.filter(types, function (type) { - return type.value == imageData.file; + return type.value === imageData.file; }), 'code'); return imageData; @@ -197,18 +210,20 @@ define([ if (this.sections().images.attribute()) { this.sections().images.attribute().chosen.each(function (option) { option.sections().images = new this.makeImages( - this.getImageProperty($('[data-role=step-gallery-option-'+option.id+']')), + this.getImageProperty($('[data-role=step-gallery-option-' + option.id + ']')), 'thumbnail' ); }, this); } break; + case 'single': this.sections().images.value(new this.makeImages( this.getImageProperty($('[data-role=step-gallery-single]')), 'thumbnail' )); break; + default: this.sections().images.value(new this.makeImages()); break; @@ -223,6 +238,7 @@ define([ wizard.data.variations = this.generateVariation(this.attributes()); }, validate: function () { + var formValid; _.each(this.sections(), function (section) { switch (section.type()) { case 'each': @@ -230,6 +246,7 @@ define([ throw new Error($.mage.__('Please, select attribute for the section ' + section.label)); } break; + case 'single': if (!section.value()) { throw new Error($.mage.__('Please fill in the values for the section ' + section.label)); @@ -237,23 +254,25 @@ define([ break; } }, this); - var formValid = true; - _.each($('[data-role=attributes-values-form]'), function(form) { + formValid = true; + _.each($('[data-role=attributes-values-form]'), function (form) { formValid = $(form).valid() && formValid; }); + if (!formValid) { throw new Error($.mage.__('Please, fill correct values')); } }, - validateImage: function() { + validateImage: function () { switch (this.sections().images.type()) { case 'each': - _.each(this.sections()['images'].attribute().chosen, function(option) { + _.each(this.sections()['images'].attribute().chosen, function (option) { if (!option.sections().images.images.length) { throw new Error($.mage.__('Please, select image(s) for your attribute')); } }); break; + case 'single': if (this.sections().images.value().file == null) { throw new Error($.mage.__('Please choose image(s)')); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/select_attributes.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/select_attributes.js index b80e8332ed282b942ae870b6171e114e0c604fc0..1715112f9cf4cf7607c67c373fe15968c02fdd59 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/select_attributes.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/select_attributes.js @@ -2,16 +2,17 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ +// jscs:disable jsDoc define([ 'uiComponent', 'jquery', 'underscore', 'mage/translate' ], function (Component, $, _) { - "use strict"; + 'use strict'; var initNewAttributeListener = function (provider) { - $('[data-role=product-variations-generator]').on('add', function() { + $('[data-role=product-variations-matrix]').on('add', function () { provider().reload(); }); }; @@ -31,7 +32,8 @@ define([ notificationMessage: { text: null, error: null - } + }, + selectedAttributes: [] }, initialize: function () { this._super(); @@ -41,6 +43,7 @@ define([ }, initObservable: function () { this._super().observe(['selectedAttributes']); + return this; }, render: function (wizard) { @@ -48,28 +51,33 @@ define([ this.setNotificationMessage(); }, setNotificationMessage: function () { - if (this.mode == 'edit') { + if (this.mode === 'edit') { this.wizard.setNotificationMessage($.mage.__('When you remove or add an attribute, we automatically ' + 'update all configurations and you will need to manually recreate the current configurations.')); } }, - doSelectSavedAttributes: function() { - if (false === this.stepInitialized) { + doSelectSavedAttributes: function () { + if (this.stepInitialized === false) { this.stepInitialized = true; //cache attributes labels, which can be present on the 2nd page - _.each(this.initData.attributes, function(attribute) { + _.each(this.initData.attributes, function (attribute) { this.attributesLabels[attribute.id] = attribute.label; }.bind(this)); this.multiselect().selected(_.pluck(this.initData.attributes, 'id')); } }, - doSelectedAttributesLabels: function(selected) { + doSelectedAttributesLabels: function (selected) { var labels = []; this.selected = selected; - _.each(selected, function(attributeId) { + _.each(selected, function (attributeId) { + var attribute; + if (!this.attributesLabels[attributeId]) { - var attribute = _.findWhere(this.multiselect().rows(), {attribute_id: attributeId}); + attribute = _.findWhere(this.multiselect().rows(), { + attribute_id: attributeId + }); + if (attribute) { this.attributesLabels[attribute.attribute_id] = attribute.frontend_label; } diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js index c04b2e9931c7c5b9804ab3ab9c63d7ce34f80ae7..0da35357808085b8fe91792cd7bdac8e523a3e37 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/summary.js @@ -2,6 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ +// jscs:disable jsDoc define([ 'uiComponent', 'jquery', @@ -33,42 +34,49 @@ define([ this.gridExisting.columns = ko.observableArray(); this.gridNew.columns = ko.observableArray(); this.gridDeleted.columns = ko.observableArray(); + return this; }, nextLabelText: $.mage.__('Generate Products'), variations: [], generateGrid: function (variations, getSectionValue) { - var productName = this.variationsComponent().getProductValue('name'); - var productPrice = this.variationsComponent().getProductValue('price'); - var productWeight = this.variationsComponent().getProductValue('weight'); - var variationsKeys = []; - var gridExisting = []; - var gridNew = []; - var gridDeleted = []; + var productName = this.variationsComponent().getProductValue('name'), + productPrice = this.variationsComponent().getProductValue('price'), + productWeight = this.variationsComponent().getProductValue('weight'), + variationsKeys = [], + gridExisting = [], + gridNew = [], + gridDeleted = []; this.variations = []; _.each(variations, function (options) { - var images, sku, quantity, price; - var productId = this.variationsComponent().getProductIdByOptions(options); + var product, images, sku, quantity, price, variation, + productId = this.variationsComponent().getProductIdByOptions(options); + if (productId) { - var product = _.findWhere(this.variationsComponent().variations, {productId: productId}); + product = _.findWhere(this.variationsComponent().variations, { + productId: productId + }); } images = getSectionValue('images', options); sku = productName + _.reduce(options, function (memo, option) { return memo + '-' + option.label; }, ''); quantity = getSectionValue('quantity', options); + if (!quantity && productId) { quantity = product.quantity; } price = getSectionValue('price', options); + if (!price) { price = productId ? product.price : productPrice; } + if (productId && !images.file) { images = product.images; } - var variation = { + variation = { options: options, images: images, sku: sku, @@ -78,6 +86,7 @@ define([ weight: productWeight, editable: true }; + if (productId) { variation.sku = product.sku; variation.weight = product.weight; @@ -99,7 +108,9 @@ define([ _.each(_.omit(this.variationsComponent().productAttributesMap, variationsKeys), function (productId) { gridDeleted.push(this.prepareRowForGrid( - _.findWhere(this.variationsComponent().variations, {productId: productId}) + _.findWhere(this.variationsComponent().variations, { + productId: productId + }) )); }.bind(this)); @@ -108,9 +119,11 @@ define([ this.gridDeleted.columns(this.getColumnsName(this.variationsComponent().productAttributes)); } }, - prepareRowForGrid: function(variation) { + prepareRowForGrid: function (variation) { var row = []; - row.push(_.extend({images: []}, variation.images)); + row.push(_.extend({ + images: [] + }, variation.images)); row.push(variation.sku); row.push(variation.quantity); _.each(variation.options, function (option) { @@ -120,10 +133,10 @@ define([ return row; }, - getGridTemplate: function() { + getGridTemplate: function () { return this.gridTemplate; }, - getGridId: function() { + getGridId: function () { return _.uniqueId('grid_'); }, getColumnsName: function (attributes) { @@ -144,11 +157,11 @@ define([ this.gridDeleted([]); this.generateGrid(wizard.data.variations, wizard.data.sectionHelper); }, - force: function (wizard) { + force: function () { this.variationsComponent().render(this.variations, this.attributes()); $('[data-role=step-wizard-dialog]').trigger('closeModal'); }, - back: function (wizard) { + back: function () { } }); }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index dea945abb299764a3ec86592abbf89d049e220ca..9688fb2bb08c33a6ad5327e59a24a8e3889a8739 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -1,7 +1,9 @@ +// jscs:disable requireDotNotation /** * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ +// jscs:disable jsDoc define([ 'uiComponent', 'jquery', @@ -10,6 +12,12 @@ define([ ], function (Component, $, ko, _) { 'use strict'; + function UserException(message) { + this.message = message; + this.name = 'UserException'; + } + UserException.prototype = Object.create(Error.prototype); + return Component.extend({ defaults: { opened: false, @@ -17,62 +25,132 @@ define([ productMatrix: [], variations: [], productAttributes: [], + fullAttributes: [], rowIndexToEdit: false, productAttributesMap: null, modules: { - associatedProductsFilter: '${ $.associatedProductsFilter }', - associatedProductsProvider: '${ $.associatedProductsProvider }' + associatedProductGrid: '${ $.configurableProductGrid }' } }, initialize: function () { this._super(); + if (this.variations.length) { this.render(this.variations, this.productAttributes); } this.initProductAttributesMap(); }, - /** - * Select different product in configurations section - * @param rowIndex - */ - selectProduct: function (rowIndex) { - var productToEdit = this.productMatrix.splice(this.rowIndexToEdit, 1)[0], - newProduct = this.associatedProductsProvider().data.items[rowIndex]; - - newProduct = _.extend(productToEdit, newProduct); - newProduct.productId = productToEdit.entity_id; - newProduct.productUrl = this.buildProductUrl(newProduct.entity_id); - newProduct.editable = false; - newProduct.images = {preview: newProduct.thumbnail_src}; - this.productAttributesMap[this.getVariationKey(newProduct.options)] = newProduct.productId; - this.productMatrix.splice(this.rowIndexToEdit, 0, newProduct); - $('#associated-products-container').trigger('closeModal'); - }, initObservable: function () { this._super().observe('actions opened attributes productMatrix'); + return this; }, + showGrid: function (rowIndex) { + var product = this.productMatrix()[rowIndex], + attributes = JSON.parse(product.attribute); + this.rowIndexToEdit = rowIndex; + + this.associatedProductGrid().open( + { + 'filters': attributes, + 'filters_modifier': product.productId ? { + 'entity_id': { + 'condition_type': 'neq', value: product.productId + } + } : {} + }, + 'changeProduct', + false + ); + }, + changeProduct: function (newProducts) { + var oldProduct = this.productMatrix()[this.rowIndexToEdit], + newProduct = this._makeProduct(_.extend(oldProduct, newProducts[0])); + this.productAttributesMap[this.getVariationKey(newProduct.options)] = newProduct.productId; + this.productMatrix.splice(this.rowIndexToEdit, 1, newProduct); + }, + appendProducts: function (newProducts) { + this.productMatrix.push.apply( + this.productMatrix, + _.map( + newProducts, + _.wrap( + this._makeProduct.bind(this), + function (func, product) { + var newProduct = func(product); + this.productAttributesMap[this.getVariationKey(newProduct.options)] = newProduct.productId; + + return newProduct; + }.bind(this) + ) + ) + ); + }, + _makeProduct: function (product) { + var productId = product['entity_id'] || product.productId || null, + attributes = _.pick(product, this.attributes.pluck('code')), + options = _.map(attributes, function (option, attribute) { + var oldOptions = _.findWhere(this.attributes(), { + code: attribute + }).options, + result; + + if (_.isFunction(oldOptions)) { + result = oldOptions.findWhere({ + value: option + }); + } else { + result = _.findWhere(oldOptions, { + value: option + }); + } + + return result; + }.bind(this)); + + return { + attribute: JSON.stringify(attributes), + editable: false, + images: { + preview: product['thumbnail_src'] + }, + name: product.name || product.sku, + options: options, + price: parseFloat(product.price.replace(/[^\d.]+/g, '')).toFixed(4), + productId: productId, + productUrl: this.buildProductUrl(productId), + quantity: product.quantity || null, + sku: product.sku, + status: product.status === undefined ? 1 : parseInt(product.status, 10), + variationKey: this.getVariationKey(options), + weight: product.weight || null + }; + }, getProductValue: function (name) { return $('[name="product[' + name.split('/').join('][') + ']"]', this.productForm).val(); }, getRowId: function (data, field) { var key = data.variationKey; + return 'variations-matrix-' + key + '-' + field; }, - getVariationRowName: function(variation, field) { + getVariationRowName: function (variation, field) { + var result; + if (variation.productId) { - return 'configurations[' + variation.productId + '][' + field.split('/').join('][') + ']'; + result = 'configurations[' + variation.productId + '][' + field.split('/').join('][') + ']'; } else { - var key = variation.variationKey; - return 'variations-matrix[' + key + '][' + field.split('/').join('][') + ']'; + result = 'variations-matrix[' + variation.variationKey + '][' + field.split('/').join('][') + ']'; } + + return result; }, getAttributeRowName: function (attribute, field) { return 'product[configurable_attributes_data][' + attribute.id + '][' + field + ']'; }, getOptionRowName: function (attribute, option, field) { - return 'product[configurable_attributes_data][' + attribute.id + '][values][' + option.value + '][' - + field + ']'; + return 'product[configurable_attributes_data][' + attribute.id + '][values][' + + option.value + '][' + field + ']'; }, render: function (variations, attributes) { this.changeButtonWizard(); @@ -85,6 +163,14 @@ define([ var $button = $('[data-action=open-steps-wizard] [data-role=button-label]'); $button.text($button.attr('data-edit-label')); }, + + /** + * Get attributes options + * @see use in matrix.phtml + * @function + * @event + * @returns {array} + */ getAttributesOptions: function () { return this.showVariations() ? this.productMatrix()[0].options : []; }, @@ -96,7 +182,8 @@ define([ _.each(variations, function (variation) { var attributes = _.reduce(variation.options, function (memo, option) { var attribute = {}; - attribute[option.attribute_code] = option.value; + attribute[option['attribute_code']] = option.value; + return _.extend(memo, attribute); }, {}); this.productMatrix.push(_.extend(variation, { @@ -104,10 +191,10 @@ define([ name: variation.name || variation.sku, weight: variation.weight, attribute: JSON.stringify(attributes), - variationKey: _.values(attributes).join('-'), + variationKey: this.getVariationKey(variation.options), editable: variation.editable === undefined ? !variation.productId : variation.editable, productUrl: this.buildProductUrl(variation.productId), - status: variation.status === undefined ? 1 : parseInt(variation.status) + status: variation.status === undefined ? 1 : parseInt(variation.status, 10) })); }, this); }, @@ -116,38 +203,35 @@ define([ }, removeProduct: function (rowIndex) { this.opened(false); - this.productMatrix.splice(rowIndex, 1); + var removedProduct = this.productMatrix.splice(rowIndex, 1); + delete this.productAttributesMap[this.getVariationKey(removedProduct[0].options)]; + if (this.productMatrix().length === 0) { - this.attributes.each(function(attribute) { + this.attributes.each(function (attribute) { $('[data-attribute-code="' + attribute.code + '"] select').removeProp('disabled'); }); } }, - showGrid: function (rowIndex) { - var attributes = JSON.parse(this.productMatrix()[rowIndex].attribute); - this.rowIndexToEdit = rowIndex; - this.associatedProductsProvider().params.attribute_ids = _.keys(attributes); - this.associatedProductsFilter().set('filters', attributes).apply(); - $('#associated-products-container').trigger('openModal'); - }, toggleProduct: function (rowIndex) { - var productChanged = {}; + var product, row, productChanged = {}; + if (this.productMatrix()[rowIndex].editable) { - var row = $('[data-row-number=' + rowIndex + ']'); - _.each('name,sku,qty,weight,price'.split(','), function (column) { + row = $('[data-row-number=' + rowIndex + ']'); + _.each(['name','sku','qty','weight','price'], function (column) { productChanged[column] = $( 'input[type=text]', row.find($('[data-column="%s"]'.replace('%s', column))) ).val(); }); } - var product = this.productMatrix.splice(rowIndex, 1)[0]; + product = this.productMatrix.splice(rowIndex, 1)[0]; product = _.extend(product, productChanged); - product.status = !product.status * 1; + product.status = +!product.status; this.productMatrix.splice(rowIndex, 0, product); }, toggleList: function (rowIndex) { var state = false; + if (rowIndex !== this.opened()) { state = rowIndex; } @@ -169,100 +253,110 @@ define([ return this.productAttributesMap[this.getVariationKey(options)] || null; }, initProductAttributesMap: function () { - if (null === this.productAttributesMap) { + if (this.productAttributesMap === null) { this.productAttributesMap = {}; - _.each(this.variations, function(product) { + _.each(this.variations, function (product) { this.productAttributesMap[this.getVariationKey(product.options)] = product.productId; }.bind(this)); } }, + + /** + * Is show preview image + * @see use in matrix.phtml + * @function + * @event + * @param {object} variation + * @returns {*|boolean} + */ isShowPreviewImage: function (variation) { return variation.images.preview && (!variation.editable || variation.images.file); }, generateImageGallery: function (variation) { - var gallery = []; - var imageFields = ['position', 'file', 'disabled', 'label']; + var gallery = [], + imageFields = ['position', 'file', 'disabled', 'label']; _.each(variation.images.images, function (image) { _.each(imageFields, function (field) { gallery.push( - '<input type="hidden" name="' - + this.getVariationRowName(variation, 'media_gallery/images/' + image.file_id + '/' + field) - + '" value="' + (image[field] || '') + '" />' + '<input type="hidden" name="' + + this.getVariationRowName(variation, 'media_gallery/images/' + image['file_id'] + '/' + field) + + '" value="' + (image[field] || '') + '" />' ); }, this); _.each(image.galleryTypes, function (imageType) { gallery.push( - '<input type="hidden" name="' + this.getVariationRowName(variation, imageType) - + '" value="' + image.file + '" />' + '<input type="hidden" name="' + this.getVariationRowName(variation, imageType) + + '" value="' + image.file + '" />' ); }, this); }, this); + return gallery.join('\n'); }, initImageUpload: function () { require([ - "jquery", - "mage/template", - "jquery/file-uploader", - "mage/mage", - "mage/translate" - ], function (jQuery, mageTemplate) { + 'mage/template', + 'jquery/file-uploader', + 'mage/mage', + 'mage/translate', + 'domReady!' + ], function (mageTemplate) { - jQuery(function ($) { - var matrix = $('[data-role=product-variations-matrix]'); - matrix.find('[data-action=upload-image]').find('[name=image]').each(function () { - var imageColumn = $(this).closest('[data-column=image]'); - if (imageColumn.find('[data-role=image]').length) { - imageColumn.find('[data-toggle=dropdown]').dropdown().show(); - } - $(this).fileupload({ - dataType: 'json', - dropZone: $(this).closest('[data-role=row]'), - acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, - done: function (event, data) { - var tmpl; + var matrix = $('[data-role=product-variations-matrix]'); + matrix.find('[data-action=upload-image]').find('[name=image]').each(function () { + var imageColumn = $(this).closest('[data-column=image]'); - if (!data.result) { - return; - } - if (!data.result.error) { - var parentElement = $(event.target).closest('[data-column=image]'), - uploaderControl = parentElement.find('[data-action=upload-image]'), - imageElement = parentElement.find('[data-role=image]'); + if (imageColumn.find('[data-role=image]').length) { + imageColumn.find('[data-toggle=dropdown]').dropdown().show(); + } + $(this).fileupload({ + dataType: 'json', + dropZone: $(this).closest('[data-role=row]'), + acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, + done: function (event, data) { + var tmpl, parentElement, uploaderControl, imageElement; + + if (!data.result) { + return; + } - if (imageElement.length) { - imageElement.attr('src', data.result.url); - } else { - tmpl = mageTemplate(matrix.find('[data-template-for=variation-image]').html()); + if (!data.result.error) { + parentElement = $(event.target).closest('[data-column=image]'); + uploaderControl = parentElement.find('[data-action=upload-image]'); + imageElement = parentElement.find('[data-role=image]'); - $(tmpl({ - data: data.result - })).prependTo(uploaderControl); - } - parentElement.find('[name$="[image]"]').val(data.result.file); - parentElement.find('[data-toggle=dropdown]').dropdown().show(); + if (imageElement.length) { + imageElement.attr('src', data.result.url); } else { - alert($.mage.__('We don\'t recognize or support this file extension type.')); + tmpl = mageTemplate(matrix.find('[data-template-for=variation-image]').html()); + + $(tmpl({ + data: data.result + })).prependTo(uploaderControl); } - }, - start: function (event) { - $(event.target).closest('[data-action=upload-image]').addClass('loading'); - }, - stop: function (event) { - $(event.target).closest('[data-action=upload-image]').removeClass('loading'); + parentElement.find('[name$="[image]"]').val(data.result.file); + parentElement.find('[data-toggle=dropdown]').dropdown().show(); + } else { + alert($.mage.__('We don\'t recognize or support this file extension type.')); } - }); - }); - matrix.find('[data-action=no-image]').click(function (event) { - var parentElement = $(event.target).closest('[data-column=image]'); - parentElement.find('[data-role=image]').remove(); - parentElement.find('[name$="[image]"]').val(''); - parentElement.find('[data-toggle=dropdown]').trigger('close.dropdown').hide(); + }, + start: function (event) { + $(event.target).closest('[data-action=upload-image]').addClass('loading'); + }, + stop: function (event) { + $(event.target).closest('[data-action=upload-image]').removeClass('loading'); + } }); }); + matrix.find('[data-action=no-image]').click(function (event) { + var parentElement = $(event.target).closest('[data-column=image]'); + parentElement.find('[data-role=image]').remove(); + parentElement.find('[name$="[image]"]').val(''); + parentElement.find('[data-toggle=dropdown]').trigger('close.dropdown').hide(); + }); }); }, - disableConfigurableAttributes: function(attributes) { + disableConfigurableAttributes: function (attributes) { _.each(attributes, function (attribute) { $('[data-attribute-code="' + attribute.code + '"] select').prop('disabled', true); }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/product/product.css b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/product/product.css index ae623978703f51d742b6776f72141fdf9d001693..0383db2fac0f00dd04464eb57a9d5e3198f92cdb 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/product/product.css +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/product/product.css @@ -163,9 +163,6 @@ #product_options_container .data-table td, #attribute-tier_price-container .data-table td, -#attribute-group_price-container .data-table td { - vertical-align: top; -} #product_options_container .data-table td.opt-price-type, #product_options_container .data-table td.select-opt-price-type, diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index b793612a5ebc066e20214840258fd831488439ff..2fd83c511c802876486a57802c6a4ffe2fec0780 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -375,13 +375,18 @@ define([ var prices = {}, elements = _.toArray(this.options.settings); + var hasProductPrice = false; _.each(elements, function(element) { var selected = element.options[element.selectedIndex], - config = selected && selected.config; + config = selected && selected.config, + priceValue = {}; - prices[element.attributeId] = config && config.allowedProducts.length === 1 - ? this._calculatePrice(config) - : {}; + if (config && config.allowedProducts.length === 1 && !hasProductPrice) { + priceValue = this._calculatePrice(config); + hasProductPrice = true; + } + + prices[element.attributeId] = priceValue; }, this); return prices; diff --git a/app/code/Magento/Contact/Block/ContactForm.php b/app/code/Magento/Contact/Block/ContactForm.php index 907bccb5a494bd97cfb654012824ea6b203b746b..59f0d225f314fcc869132416750a83193f9ac2ef 100644 --- a/app/code/Magento/Contact/Block/ContactForm.php +++ b/app/code/Magento/Contact/Block/ContactForm.php @@ -21,4 +21,14 @@ class ContactForm extends Template parent::__construct($context, $data); $this->_isScopePrivate = true; } + + /** + * Returns action url for contact form + * + * @return string + */ + public function getFormAction() + { + return $this->getUrl('contact/index/post', ['_secure' => true]); + } } diff --git a/app/code/Magento/Contact/Controller/Index/Index.php b/app/code/Magento/Contact/Controller/Index/Index.php index b6e6e3f0ffaa0f52c14c3b18c3039e4dda615459..247728f63f881cbc70817c1ada56bd81702db6a4 100644 --- a/app/code/Magento/Contact/Controller/Index/Index.php +++ b/app/code/Magento/Contact/Controller/Index/Index.php @@ -16,9 +16,6 @@ class Index extends \Magento\Contact\Controller\Index public function execute() { $this->_view->loadLayout(); - $this->_view->getLayout() - ->getBlock('contactForm') - ->setFormAction($this->_url->getUrl('*/*/post', ['_secure' => true])); $this->_view->renderLayout(); } } diff --git a/app/code/Magento/Contact/Controller/Index/Post.php b/app/code/Magento/Contact/Controller/Index/Post.php index b6fae4cad8a635a102cc7ccb53b51b122b7759cc..f5114cd6f68b91b98557cdebf899e3ffbba5a90f 100644 --- a/app/code/Magento/Contact/Controller/Index/Post.php +++ b/app/code/Magento/Contact/Controller/Index/Post.php @@ -65,14 +65,14 @@ class Post extends \Magento\Contact\Controller\Index $this->messageManager->addSuccess( __('Thanks for contacting us with your comments and questions. We\'ll respond to you very soon.') ); - $this->_redirect('*/*/'); + $this->_redirect('contact/index'); return; } catch (\Exception $e) { $this->inlineTranslation->resume(); $this->messageManager->addError( __('We can\'t process your request right now. Sorry, that\'s all we know.') ); - $this->_redirect('*/*/'); + $this->_redirect('contact/index'); return; } } diff --git a/app/code/Magento/Contact/Test/Unit/Block/ContactFormTest.php b/app/code/Magento/Contact/Test/Unit/Block/ContactFormTest.php index 446279ee14ef75cbd78709c7f8bbc521aeede2cb..e710a47153e7bb4df7eac61af0d92af65326bba2 100644 --- a/app/code/Magento/Contact/Test/Unit/Block/ContactFormTest.php +++ b/app/code/Magento/Contact/Test/Unit/Block/ContactFormTest.php @@ -20,6 +20,11 @@ class ContactFormTest extends \PHPUnit_Framework_TestCase */ protected $contextMock; + /** + * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $urlBuilderMock; + /** * {@inheritDoc} */ @@ -27,8 +32,17 @@ class ContactFormTest extends \PHPUnit_Framework_TestCase { $this->contextMock = $this->getMockBuilder('Magento\Framework\View\Element\Template\Context') ->disableOriginalConstructor() + ->setMethods(['getUrlBuilder']) ->getMock(); + $this->urlBuilderMock = $this->getMockBuilder('Magento\Framework\UrlInterface') + ->disableOriginalConstructor() + ->getMock(); + + $this->contextMock->expects($this->any()) + ->method('getUrlBuilder') + ->willReturn($this->urlBuilderMock); + $this->contactForm = new ContactForm( $this->contextMock ); @@ -41,4 +55,15 @@ class ContactFormTest extends \PHPUnit_Framework_TestCase { $this->assertTrue($this->contactForm->isScopePrivate()); } + + /** + * @return void + */ + public function testGetFormAction() + { + $this->urlBuilderMock->expects($this->once()) + ->method('getUrl') + ->with('contact/index/post', ['_secure' => true]); + $this->contactForm->getFormAction(); + } } diff --git a/app/code/Magento/Contact/Test/Unit/Controller/Index/IndexTest.php b/app/code/Magento/Contact/Test/Unit/Controller/Index/IndexTest.php index ccaec64fabb98588414aac86ae67c5e8b14bd956..a914cf698e33b4458a4248b2d424f79c5259cb22 100644 --- a/app/code/Magento/Contact/Test/Unit/Controller/Index/IndexTest.php +++ b/app/code/Magento/Contact/Test/Unit/Controller/Index/IndexTest.php @@ -93,31 +93,9 @@ class IndexTest extends \PHPUnit_Framework_TestCase public function testExecute() { - $layout = $this->getMock( - '\Magento\Framework\View\Layout', - ['getBlock', 'initMessages'], - [], - '', - false - ); - $block = $this->getMockForAbstractClass( - '\Magento\Framework\View\Element\AbstractBlock', - ['setFormAction'], - '', - false - ); - $layout->expects($this->once()) - ->method('getBlock') - ->with('contactForm') - ->will($this->returnValue($block)); - $this->_view->expects($this->once()) ->method('loadLayout'); - $this->_view->expects($this->exactly(1)) - ->method('getLayout') - ->will($this->returnValue($layout)); - $this->_view->expects($this->once()) ->method('renderLayout'); diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index 0b7d86582338566b75aa59fccb447abeb71938af..27185c09c5557bef4f95f53f5171d735e779c843 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -5,6 +5,7 @@ "php": "~5.5.0|~5.6.0|~7.0.0", "magento/module-config": "1.0.0-beta", "magento/module-store": "1.0.0-beta", + "magento/module-backend": "1.0.0-beta", "magento/module-customer": "1.0.0-beta", "magento/module-cms": "1.0.0-beta", "magento/framework": "1.0.0-beta", diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index b1afc6a19518bbc523a02f2c0db4f669510a0e67..653e1f8443cbba8f049eb397c299bde7ec9ac3f6 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -3,11 +3,13 @@ "description": "N/A", "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "magento/module-config": "1.0.0-beta", "magento/module-store": "1.0.0-beta", "magento/framework": "1.0.0-beta", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-config": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php b/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..c07ca309622d5735dc3db4153440a956ada5c691 --- /dev/null +++ b/app/code/Magento/Customer/Console/Command/UpgradeHashAlgorithmCommand.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Console\Command; + +use Magento\Customer\Model\Customer; +use Magento\Framework\Encryption\Encryptor; +use Magento\Customer\Model\Resource\Customer\Collection; +use Magento\Customer\Model\Resource\Customer\CollectionFactory; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class UpgradeHashAlgorithmCommand extends Command +{ + /** + * @var CollectionFactory + */ + private $customerCollectionFactory; + + /** + * @var Collection + */ + private $collection; + + /** + * @var Encryptor + */ + private $encryptor; + + /** + * @param CollectionFactory $customerCollectionFactory + * @param Encryptor $encryptor + */ + public function __construct( + CollectionFactory $customerCollectionFactory, + Encryptor $encryptor + ) { + parent::__construct(); + $this->customerCollectionFactory = $customerCollectionFactory; + $this->collection = $customerCollectionFactory->create(); + $this->encryptor = $encryptor; + } + + /** + * @inheritdoc + */ + protected function configure() + { + $this->setName('customer:hash:upgrade') + ->setDescription('Upgrade customer\'s hash according to the latest algorithm'); + } + + /** + * @inheritdoc + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->collection->addAttributeToSelect('*'); + $customerCollection = $this->collection->getItems(); + /** @var $customer Customer */ + foreach ($customerCollection as $customer) { + $customer->load($customer->getId()); + if (!$this->encryptor->validateHashVersion($customer->getPasswordHash())) { + list($hash, $salt, $version) = explode(Encryptor::DELIMITER, $customer->getPasswordHash(), 3); + $version .= Encryptor::DELIMITER . Encryptor::HASH_VERSION_LATEST; + $customer->setPasswordHash($this->encryptor->getHash($hash, $salt, $version)); + $customer->save(); + $output->write("."); + } + } + $output->writeln("."); + $output->writeln("<info>Finished</info>"); + } +} diff --git a/app/code/Magento/Customer/Controller/Section/Load.php b/app/code/Magento/Customer/Controller/Section/Load.php index 6b5fb9e916a7e81457a2facf2c64200100929c68..df4abe308baa59d433515b52d4ab5cbd72fa20da 100644 --- a/app/code/Magento/Customer/Controller/Section/Load.php +++ b/app/code/Magento/Customer/Controller/Section/Load.php @@ -20,6 +20,11 @@ class Load extends \Magento\Framework\App\Action\Action */ protected $resultJsonFactory; + /** + * @var Identifier + */ + protected $sectionIdentifier; + /** * @var SectionPoolInterface */ @@ -28,15 +33,18 @@ class Load extends \Magento\Framework\App\Action\Action /** * @param Context $context * @param JsonFactory $resultJsonFactory + * @param \Magento\Customer\CustomerData\Section\Identifier $sectionIdentifier * @param SectionPoolInterface $sectionPool */ public function __construct( Context $context, JsonFactory $resultJsonFactory, + \Magento\Customer\CustomerData\Section\Identifier $sectionIdentifier, SectionPoolInterface $sectionPool ) { parent::__construct($context); $this->resultJsonFactory = $resultJsonFactory; + $this->sectionIdentifier = $sectionIdentifier; $this->sectionPool = $sectionPool; } @@ -51,7 +59,11 @@ class Load extends \Magento\Framework\App\Action\Action $sectionNames = $this->getRequest()->getParam('sections'); $sectionNames = $sectionNames ? array_unique(\explode(',', $sectionNames)) : null; - $response = $this->sectionPool->getSectionsData($sectionNames); + $updateSectionId = $this->getRequest()->getParam('update_section_id'); + if ('false' == $updateSectionId) { + $updateSectionId = false; + } + $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$updateSectionId); } catch (\Exception $e) { $resultJson->setStatusHeader( \Zend\Http\Response::STATUS_CODE_400, diff --git a/app/code/Magento/Customer/CustomerData/Section/Identifier.php b/app/code/Magento/Customer/CustomerData/Section/Identifier.php new file mode 100644 index 0000000000000000000000000000000000000000..48da030992c8cb3d99c49f151da3a69797e88832 --- /dev/null +++ b/app/code/Magento/Customer/CustomerData/Section/Identifier.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\CustomerData\Section; + +/** + * Customer section identifier + */ +class Identifier +{ + const COOKIE_KEY = 'storage_data_id'; + + const SECTION_KEY = 'data_id'; + + const UPDATE_MARK = 'sections_updated'; + + /** + * @var int + */ + protected $markId; + + /** + * @var \Magento\Framework\Stdlib\Cookie\PhpCookieManager + */ + protected $cookieManager; + + /** + * @var \Magento\Framework\Session\Config\ConfigInterface + */ + protected $sessionConfig; + + /** + * @param \Magento\Framework\Stdlib\Cookie\PhpCookieManager $cookieManager + */ + public function __construct( + \Magento\Framework\Stdlib\Cookie\PhpCookieManager $cookieManager + ) { + $this->cookieManager = $cookieManager; + } + + /** + * Init mark(identifier) for sections + * + * @param bool $forceUpdate + * @return int + */ + public function initMark($forceUpdate) + { + if ($forceUpdate) { + $this->markId = time(); + return $this->markId; + } + + $cookieMarkId = false; + if (!$this->markId) { + $cookieMarkId = $this->cookieManager->getCookie(self::COOKIE_KEY); + } + + $this->markId = $cookieMarkId ? $cookieMarkId : time(); + + return $this->markId; + } + + /** + * Mark sections with data id + * + * @param array $sectionsData + * @param null $sectionNames + * @param bool $updateIds + * @return array + */ + public function markSections(array $sectionsData, $sectionNames = null, $updateIds = false) + { + if (!$sectionNames) { + $sectionNames = array_keys($sectionsData); + } + $markId = $this->initMark($updateIds); + + foreach ($sectionNames as $name) { + if ($updateIds || !array_key_exists(self::SECTION_KEY, $sectionsData[$name])) { + $sectionsData[$name][self::SECTION_KEY] = $markId; + } + } + return $sectionsData; + } +} diff --git a/app/code/Magento/Customer/CustomerData/SectionPool.php b/app/code/Magento/Customer/CustomerData/SectionPool.php index 419ee1be5f163d4bbcc9a03bfa558e9fec1379c3..7c4fd906922be56d5410378c2f5dff93647fbd6e 100644 --- a/app/code/Magento/Customer/CustomerData/SectionPool.php +++ b/app/code/Magento/Customer/CustomerData/SectionPool.php @@ -27,24 +27,36 @@ class SectionPool implements SectionPoolInterface */ protected $sectionSourceMap; + /** + * @var \Magento\Customer\CustomerData\Section\Identifier + */ + protected $identifier; + /** * Construct * * @param ObjectManagerInterface $objectManager + * @param \Magento\Customer\CustomerData\Section\Identifier $identifier * @param array $sectionSourceMap */ - public function __construct(ObjectManagerInterface $objectManager, array $sectionSourceMap = []) - { + public function __construct( + ObjectManagerInterface $objectManager, + \Magento\Customer\CustomerData\Section\Identifier $identifier, + array $sectionSourceMap = [] + ) { $this->objectManager = $objectManager; + $this->identifier = $identifier; $this->sectionSourceMap = $sectionSourceMap; } /** * {@inheritdoc} */ - public function getSectionsData(array $sectionNames = null) + public function getSectionsData(array $sectionNames = null, $updateIds = false) { - return $sectionNames ? $this->getSectionDataByNames($sectionNames) : $this->getAllSectionData(); + $sectionsData = $sectionNames ? $this->getSectionDataByNames($sectionNames) : $this->getAllSectionData(); + $sectionsData = $this->identifier->markSections($sectionsData, $sectionNames, $updateIds); + return $sectionsData; } /** diff --git a/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php b/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php index 4c0c6647824ee2af03114f075ab96be5e417109e..453dcb146461719d3646de23350ebfaa5bed6b2f 100644 --- a/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php +++ b/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php @@ -14,7 +14,8 @@ interface SectionPoolInterface * Get section data by section names. If $sectionNames is null then return all sections data * * @param array $sectionNames + * @param bool $updateIds * @return array */ - public function getSectionsData(array $sectionNames = null); + public function getSectionsData(array $sectionNames = null, $updateIds = false); } diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php old mode 100644 new mode 100755 diff --git a/app/code/Magento/Customer/Model/Observer.php b/app/code/Magento/Customer/Model/Observer.php index c709130edb9de682347459087084a21b92cbcf96..6fb4470a46d84882da79acf8a741c1cd3b2a1c7f 100644 --- a/app/code/Magento/Customer/Model/Observer.php +++ b/app/code/Magento/Customer/Model/Observer.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Model; +use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\GroupManagementInterface; use Magento\Customer\Helper\Address as HelperAddress; use Magento\Customer\Model\Address\AbstractAddress; @@ -13,16 +14,17 @@ use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\State as AppState; use Magento\Framework\DataObject; -use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Escaper; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\Registry; use Magento\Store\Model\ScopeInterface; +use Magento\Framework\Event\Observer as EventObserver; /** * Customer Observer Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ class Observer { @@ -83,6 +85,16 @@ class Observer */ protected $encryptor; + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + /** * @param Vat $customerVat * @param HelperAddress $customerAddress @@ -93,6 +105,8 @@ class Observer * @param Escaper $escaper * @param AppState $appState * @param EncryptorInterface $encryptor + * @param CustomerRegistry $customerRegistry + * @param CustomerRepositoryInterface $customerRepository */ public function __construct( Vat $customerVat, @@ -103,7 +117,9 @@ class Observer ManagerInterface $messageManager, Escaper $escaper, AppState $appState, - EncryptorInterface $encryptor + EncryptorInterface $encryptor, + CustomerRegistry $customerRegistry, + CustomerRepositoryInterface $customerRepository ) { $this->_customerVat = $customerVat; $this->_customerAddress = $customerAddress; @@ -114,6 +130,8 @@ class Observer $this->escaper = $escaper; $this->appState = $appState; $this->encryptor = $encryptor; + $this->customerRegistry = $customerRegistry; + $this->customerRepository = $customerRepository; } /** @@ -347,16 +365,15 @@ class Observer */ public function upgradeCustomerPassword($observer) { - $password = $observer->getEvent()->getPassword(); - /** @var \Magento\Customer\Model\Customer $model */ - $model = $observer->getEvent()->getModel(); - $isValidHash = $this->encryptor->isValidHashByVersion( - $password, - $model->getPasswordHash(), - Encryptor::HASH_VERSION_LATEST - ); - if (!$isValidHash) { - $model->changePassword($password); + $password = $observer->getEvent()->getData('password'); + /** @var Customer $model */ + $model = $observer->getEvent()->getData('model'); + $customer = $this->customerRepository->getById($model->getId()); + $customerSecure = $this->customerRegistry->retrieveSecureData($model->getId()); + + if (!$this->encryptor->validateHashVersion($customerSecure->getPasswordHash(), true)) { + $customerSecure->setPasswordHash($this->encryptor->getHash($password, true)); + $this->customerRepository->save($customer); } } } diff --git a/app/code/Magento/Customer/Model/Resource/CustomerRepository.php b/app/code/Magento/Customer/Model/Resource/CustomerRepository.php index 45bca42044092acc519258e045ebcdd864e70782..429210512de90ab81b93290918b34e533ba1d879 100644 --- a/app/code/Magento/Customer/Model/Resource/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/Resource/CustomerRepository.php @@ -340,8 +340,7 @@ class CustomerRepository implements \Magento\Customer\Api\CustomerRepositoryInte $isEmailAddress = \Zend_Validate::is( $customer->getEmail(), - 'EmailAddress', - ['allow' => ['allow' => \Zend_Validate_Hostname::ALLOW_ALL, 'tld' => false]] + 'EmailAddress' ); if (!$isEmailAddress) { diff --git a/app/code/Magento/Customer/Setup/UpgradeData.php b/app/code/Magento/Customer/Setup/UpgradeData.php index b6d9fac5c463fb4981b3462e79cd76b912b7872b..a37210bd9fb8842bb56515853295cea334d0cad0 100644 --- a/app/code/Magento/Customer/Setup/UpgradeData.php +++ b/app/code/Magento/Customer/Setup/UpgradeData.php @@ -7,6 +7,7 @@ namespace Magento\Customer\Setup; use Magento\Customer\Model\Customer; +use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Setup\UpgradeDataInterface; use Magento\Framework\Setup\ModuleContextInterface; @@ -227,6 +228,19 @@ class UpgradeData implements UpgradeDataInterface ); } + if (version_compare($context->getVersion(), '2.0.5', '<')) { + $this->upgradeHash($setup); + $entityAttributes = [ + 'customer_address' => [ + 'fax' => [ + 'is_visible' => false, + 'is_system' => false, + ], + ], + ]; + $this->upgradeAttributes($entityAttributes, $customerSetup); + } + $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); $indexer->reindexAll(); $this->eavConfig->clear(); @@ -250,4 +264,34 @@ class UpgradeData implements UpgradeDataInterface } } } + + /** + * @param ModuleDataSetupInterface $setup + * @return void + */ + private function upgradeHash($setup) + { + $customerEntityTable = $setup->getTable('customer_entity'); + + $select = $setup->getConnection()->select()->from( + $customerEntityTable, + ['entity_id', 'password_hash'] + ); + + $customers = $setup->getConnection()->fetchAll($select); + foreach ($customers as $customer) { + list($hash, $salt) = explode(Encryptor::DELIMITER, $customer['password_hash']); + + $newHash = $customer['password_hash']; + if (strlen($hash) === 32) { + $newHash = implode(Encryptor::DELIMITER, [$hash, $salt, Encryptor::HASH_VERSION_MD5]); + } elseif (strlen($hash) === 64) { + $newHash = implode(Encryptor::DELIMITER, [$hash, $salt, Encryptor::HASH_VERSION_SHA256]); + } + + $bind = ['password_hash' => $newHash]; + $where = ['entity_id = ?' => (int)$customer['entity_id']]; + $setup->getConnection()->update($customerEntityTable, $bind, $where); + } + } } diff --git a/app/code/Magento/Customer/Test/Unit/Console/Command/UpgradeHashAlgorithmCommandTest.php b/app/code/Magento/Customer/Test/Unit/Console/Command/UpgradeHashAlgorithmCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fa83775eeb2058793c96777aeb516135f9f52186 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Console/Command/UpgradeHashAlgorithmCommandTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Console\Command; + +use Magento\Customer\Console\Command\UpgradeHashAlgorithmCommand; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Customer\Model\Resource\Customer\CollectionFactory; + +class UpgradeHashAlgorithmCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var UpgradeHashAlgorithmCommand + */ + private $command; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerCollectionFactory; + + public function setUp() + { + $this->customerCollectionFactory = $this->getMockBuilder( + 'Magento\Customer\Model\Resource\Customer\CollectionFactory' + )->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManager = new ObjectManager($this); + + $this->command = $this->objectManager->getObject( + 'Magento\Customer\Console\Command\UpgradeHashAlgorithmCommand', + [ + 'customerCollectionFactory' => $this->customerCollectionFactory + ] + ); + } + + public function testConfigure() + { + $this->assertEquals('customer:hash:upgrade', $this->command->getName()); + $this->assertEquals( + 'Upgrade customer\'s hash according to the latest algorithm', + $this->command->getDescription() + ); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/CustomerData/Section/IdentifierTest.php b/app/code/Magento/Customer/Test/Unit/CustomerData/Section/IdentifierTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bedf1a9c0cc3ee8e2f2799d54e5ad4f9ae63e93b --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/CustomerData/Section/IdentifierTest.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\CustomerData\Section; + +use \Magento\Customer\CustomerData\Section\Identifier; + +class IdentifierTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Customer\CustomerData\Section\Identifier + */ + protected $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $cookieManMock; + + /** + * @var string + */ + protected $cookieMarkId; + + protected function setUp() + { + $this->cookieManMock = $this->getMock('Magento\Framework\Stdlib\Cookie\PhpCookieManager', [], [], '', false); + $this->cookieMarkId = '123456'; + $this->model = new Identifier( + $this->cookieManMock + ); + } + + public function testInitMark() + { + $this->cookieManMock->expects($this->once()) + ->method('getCookie') + ->with(Identifier::COOKIE_KEY) + ->willReturn($this->cookieMarkId); + $this->assertEquals($this->cookieMarkId, $this->model->initMark(false)); + } + + public function testMarkSectionsDontUpdate() + { + $sectionsData = [ + 'section1' => [1], + 'section2' => [2], + 'section3' => [3], + ]; + + $expectedData = [ + 'section1' => [1, 'data_id' => $this->cookieMarkId], + 'section2' => [2, 'data_id' => $this->cookieMarkId], + 'section3' => [3], + ]; + $sectionNames = ['section1', 'section2']; + + $this->cookieManMock->expects($this->once()) + ->method('getCookie') + ->with(Identifier::COOKIE_KEY) + ->willReturn($this->cookieMarkId); + + // third parameter is true to avoid diving deeply into initMark() + $result = $this->model->markSections($sectionsData, $sectionNames, false); + $this->assertEquals($expectedData, $result); + } + + public function testMarkSectionsUpdate() + { + $sectionsData = [ + 'section1' => [1, 'data_id' => 0], + 'section2' => [2, 'data_id' => 0], + 'section3' => [3], + ]; + $sectionNames = ['section1', 'section2']; + + // third parameter is true to avoid diving deeply into initMark() + $result = $this->model->markSections($sectionsData, $sectionNames, true); + $this->assertArrayHasKey('data_id', $result['section1']); + $this->assertNotEquals(0, $result['section1']['data_id']); + $this->assertArrayHasKey('data_id', $result['section2']); + $this->assertNotEquals(0, $result['section2']['data_id']); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php b/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php new file mode 100644 index 0000000000000000000000000000000000000000..70b62b05823621693073d6acbd4ee5d8a6dc11d6 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\CustomerData; + +use Magento\Customer\CustomerData\SectionPool; + +class SectionPoolTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $objectManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $identifierMock; + + /** + * @var array|null + */ + protected $sectionSourceMap; + + /** + * @var SectionPool + */ + protected $model; + + protected function setUp() + { + $this->objectManagerMock = $this->getMock('\Magento\Framework\ObjectManagerInterface', [], [], '', false); + $this->identifierMock = $this->getMock('\Magento\Customer\CustomerData\Section\Identifier', [], [], '', false); + $this->sectionSourceMap = ['section1' => 'b']; + $this->model = new SectionPool( + $this->objectManagerMock, + $this->identifierMock, + $this->sectionSourceMap + ); + } + + public function testGetSectionsDataAllSections() + { + $sectionNames = ['section1']; + $sectionsData = ['data1', 'data2']; + $allSectionsData = [ + 'section1' => [ + 'data1', + 'data2' + ] + ]; + $identifierResult = [1, 2, 3]; + + $sectionSourceMock = $this->getMock('\Magento\Customer\CustomerData\SectionSourceInterface', [], [], '', false); + $this->objectManagerMock->expects($this->once()) + ->method('get') + ->with('b') + ->willReturn($sectionSourceMock); + $sectionSourceMock->expects($this->once())->method('getSectionData')->willReturn($sectionsData); + + $this->identifierMock->expects($this->once()) + ->method('markSections') + //check also default value for $updateIds = false + ->with($allSectionsData, $sectionNames, false) + ->willReturn($identifierResult); + $modelResult = $this->model->getSectionsData($sectionNames); + $this->assertEquals($identifierResult, $modelResult); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage b doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface + */ + public function testGetSectionsDataAllSectionsException() + { + $sectionNames = []; + $identifierResult = [1, 2, 3]; + $this->objectManagerMock->expects($this->once()) + ->method('get') + ->with('b') + ->willReturn($this->model); + $modelResult = $this->model->getSectionsData($sectionNames); + $this->assertEquals($identifierResult, $modelResult); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php old mode 100644 new mode 100755 diff --git a/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php b/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php index 6e50fdaf1f7df66881a859c20b5e87d772d468f7..92b8905e904d92657f50cafb31a61a4219de309d 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php @@ -74,9 +74,14 @@ class ObserverTest extends \PHPUnit_Framework_TestCase protected $encryptorMock; /** - * @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Customer\Model\CustomerRegistry|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerMock; + protected $customerRegistry; + + /** + * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $customerRepository; protected function setUp() { @@ -109,18 +114,17 @@ class ObserverTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\Encryptor') + $this->encryptorMock = $this->getMockBuilder('Magento\Framework\Encryption\Encryptor') + ->disableOriginalConstructor() + ->getMock(); + + $this->customerRegistry = $this->getMockBuilder('Magento\Customer\Model\CustomerRegistry') ->disableOriginalConstructor() ->getMock(); - $this->encryptorMock->expects($this->any()) - ->method('isValidHashByVersion') - ->will( - $this->returnCallback( - function ($arg1, $arg2) { - return $arg1 == $arg2; - } - ) - ); + + $this->customerRepository = $this->getMockBuilder('Magento\Customer\Api\CustomerRepositoryInterface') + ->getMockForAbstractClass(); + $this->model = new Observer( $this->vat, $this->helperAddress, @@ -130,7 +134,9 @@ class ObserverTest extends \PHPUnit_Framework_TestCase $this->messageManager, $this->escaper, $this->appState, - $this->encryptorMock + $this->encryptorMock, + $this->customerRegistry, + $this->customerRepository ); } @@ -770,44 +776,59 @@ class ObserverTest extends \PHPUnit_Framework_TestCase ]; } - /** - * Test successfully password change if new password doesn't match old one - */ public function testUpgradeCustomerPassword() { - $customerMock = $this->getMockBuilder('\Magento\Customer\Model\Customer') + $customerId = '1'; + $password = 'password'; + $passwordHash = 'hash:salt:999'; + + $model = $this->getMockBuilder('Magento\Customer\Model\Customer') ->disableOriginalConstructor() - ->setMethods(['getPasswordHash', 'changePassword', '__wakeup']) + ->setMethods(['getId']) ->getMock(); - $customerMock->expects($this->once())->method('changePassword')->will($this->returnSelf()); - $customerMock->expects($this->once())->method('getPasswordHash')->will($this->returnValue('old password')); - - $event = new \Magento\Framework\DataObject(); - $event->setData(['password' => 'different password', 'model' => $customerMock]); - - $observerMock = new \Magento\Framework\DataObject(); - $observerMock->setData('event', $event); - - $this->model->upgradeCustomerPassword($observerMock); - } - - /** - * Test failure password change if new password matches old one - */ - public function testUpgradeCustomerPasswordNotChanged() - { - $customerMock = $this->getMockBuilder('\Magento\Customer\Model\Customer') + $customer = $this->getMockBuilder('Magento\Customer\Api\Data\CustomerInterface') + ->getMockForAbstractClass(); + $customerSecure = $this->getMockBuilder('Magento\Customer\Model\Data\CustomerSecure') ->disableOriginalConstructor() - ->setMethods(['getPasswordHash', 'changePassword', '__wakeup']) + ->setMethods(['getPasswordHash', 'setPasswordHash']) ->getMock(); - $customerMock->expects($this->never())->method('changePassword'); - $customerMock->expects($this->once())->method('getPasswordHash')->will($this->returnValue('same password')); + + + $model->expects($this->exactly(2)) + ->method('getId') + ->willReturn($customerId); + $this->customerRepository->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customer); + $this->customerRegistry->expects($this->once()) + ->method('retrieveSecureData') + ->with($customerId) + ->willReturn($customerSecure); + $customerSecure->expects($this->once()) + ->method('getPasswordHash') + ->willReturn($passwordHash); + $this->encryptorMock->expects($this->once()) + ->method('validateHashVersion') + ->with($passwordHash) + ->willReturn(false); + $this->encryptorMock->expects($this->once()) + ->method('getHash') + ->with($password, true) + ->willReturn($passwordHash); + $customerSecure->expects($this->once()) + ->method('setPasswordHash') + ->with($passwordHash); + $this->customerRepository->expects($this->once()) + ->method('save') + ->with($customer); $event = new \Magento\Framework\DataObject(); - $event->setData(['password' => 'same password', 'model' => $customerMock]); + $event->setData(['password' => 'password', 'model' => $model]); $observerMock = new \Magento\Framework\DataObject(); $observerMock->setData('event', $event); + $this->model->upgradeCustomerPassword($observerMock); } } diff --git a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php index e25b1105ea2efa870b8b1f3c5629d96a6619326b..45f9f7566344512b39f3ccf4211269f9f1328828 100644 --- a/app/code/Magento/Customer/Ui/Component/ColumnFactory.php +++ b/app/code/Magento/Customer/Ui/Component/ColumnFactory.php @@ -30,7 +30,7 @@ class ColumnFactory 'text' => 'text', 'boolean' => 'select', 'select' => 'select', - 'multiselect' => 'text', + 'multiselect' => 'select', 'date' => 'date', ]; diff --git a/app/code/Magento/Customer/Ui/Component/DataProvider.php b/app/code/Magento/Customer/Ui/Component/DataProvider.php index 91a14122678c9ea128c5263a30c86bc68ce4c845..16fcaa639690231160ee7eba69ca9c0919c0c59a 100644 --- a/app/code/Magento/Customer/Ui/Component/DataProvider.php +++ b/app/code/Magento/Customer/Ui/Component/DataProvider.php @@ -58,59 +58,16 @@ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvi ); } - /** - * Retrieve custom attributes options - * - * @return array - */ - public function getCustomAttributesOptions() - { - $attributes = []; - foreach ($this->attributeRepository->getList() as $attributeCode => $attributeData) { - if ($attributeData[AttributeMetadataInterface::BACKEND_TYPE] != 'static' - && $attributeData[AttributeMetadataInterface::IS_USED_IN_GRID] - && $attributeData[AttributeMetadataInterface::OPTIONS] - ) { - $attributes[$attributeCode] = $attributeData[AttributeMetadataInterface::OPTIONS]; - } - } - return $attributes; - } - - /** - * Retrieve attribute option label by option value - * - * @param array $attributeData - * @param string $value - * @return string - */ - protected function getAttributeOptionLabelByValue(array $attributeData, $value) - { - if (empty($value)) { - return $value; - } - foreach ($attributeData as $option) { - if ($option['value'] === $value) { - return $option['label']; - } - } - return $value; - } - /** * {@inheritdoc} */ public function getData() { - $data = $this->searchResultToOutput($this->getSearchResult()); - $customAttributesOptions = $this->getCustomAttributesOptions(); - foreach ($customAttributesOptions as $attributeCode => $attributeData) { + $data = parent::getData(); + foreach ($this->attributeRepository->getList() as $attributeCode => $attributeData) { foreach ($data['items'] as &$item) { - if (isset($item[$attributeCode])) { - $item[$attributeCode] = $this->getAttributeOptionLabelByValue( - $attributeData, - $item[$attributeCode] - ); + if (isset($item[$attributeCode]) && !empty($attributeData[AttributeMetadataInterface::OPTIONS])) { + $item[$attributeCode] = explode(',', $item[$attributeCode]); } } } diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index 3deee7dcbfb976e58129d55da08df24aa318f276..d767e8ec521a4d59f3b54a055fd15e00463f7458 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -308,4 +308,11 @@ <argument name="resourceModel" xsi:type="string">Magento\Customer\Model\Resource\Visitor</argument> </arguments> </type> + <type name="Magento\Framework\Console\CommandList"> + <arguments> + <argument name="commands" xsi:type="array"> + <item name="upgradeHashAlgorithmCommand" xsi:type="object">Magento\Customer\Console\Command\UpgradeHashAlgorithmCommand</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml index 01a431af183183e849593f996316296720a9a0bf..a41e8a12c45094a46b97c28b720b05e4d2e5d9e5 100644 --- a/app/code/Magento/Customer/etc/module.xml +++ b/app/code/Magento/Customer/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> - <module name="Magento_Customer" setup_version="2.0.4"> + <module name="Magento_Customer" setup_version="2.0.5"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Directory"/> diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_online_grid.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_online_grid.xml index 1144b3c1ed13dfcd518468d17fe43795c90f7fce..b144ac27d89bb6a514f786631375d9765ab0432d 100644 --- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_online_grid.xml +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_online_grid.xml @@ -267,18 +267,18 @@ </item> </argument> </column> - <column name="remote_addr"> - <argument name="data" xsi:type="array"> - <item name="js_config" xsi:type="array"> - <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/column</item> - </item> - <item name="config" xsi:type="array"> - <item name="dataType" xsi:type="string">text</item> - <item name="align" xsi:type="string">left</item> - <item name="label" xsi:type="string" translate="true">IP Address</item> - </item> - </argument> - </column> + <!--<column name="remote_addr">--> + <!--<argument name="data" xsi:type="array">--> + <!--<item name="js_config" xsi:type="array">--> + <!--<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/column</item>--> + <!--</item>--> + <!--<item name="config" xsi:type="array">--> + <!--<item name="dataType" xsi:type="string">text</item>--> + <!--<item name="align" xsi:type="string">left</item>--> + <!--<item name="label" xsi:type="string" translate="true">IP Address</item>--> + <!--</item>--> + <!--</argument>--> + <!--</column>--> <column name="first_visit_at"> <argument name="data" xsi:type="array"> <item name="js_config" xsi:type="array"> @@ -315,17 +315,17 @@ </item> </argument> </column> - <column name="last_url"> - <argument name="data" xsi:type="array"> - <item name="js_config" xsi:type="array"> - <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/column</item> - </item> - <item name="config" xsi:type="array"> - <item name="dataType" xsi:type="string">text</item> - <item name="align" xsi:type="string">left</item> - <item name="label" xsi:type="string" translate="true">Last URL</item> - </item> - </argument> - </column> + <!--<column name="last_url">--> + <!--<argument name="data" xsi:type="array">--> + <!--<item name="js_config" xsi:type="array">--> + <!--<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/column</item>--> + <!--</item>--> + <!--<item name="config" xsi:type="array">--> + <!--<item name="dataType" xsi:type="string">text</item>--> + <!--<item name="align" xsi:type="string">left</item>--> + <!--<item name="label" xsi:type="string" translate="true">Last URL</item>--> + <!--</item>--> + <!--</argument>--> + <!--</column>--> </columns> </listing> diff --git a/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml b/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml index 4d208d4136c981d6204278a72dd19139d3afb926..8bc96494817b8a969c280243a6248a190b7b38dc 100644 --- a/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/account/customer.phtml @@ -19,7 +19,8 @@ <span data-bind="text: customer().fullname"></span> <button type="button" class="action switch" - tabindex="-1"> + tabindex="-1" + data-action="customer-menu-toggle"> <span><?php /* @escapeNotVerified */ echo __('Change')?></span> </button> </span> diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml index 4452a8b46fa6ad07c7ecdde0b47f7bb8fe9da194..b5d537a55956ad0fe2a5b10c48fe8936a04b6d1d 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml @@ -14,7 +14,7 @@ <?php $options = $block->getGenderOptions(); ?> <?php $value = $block->getGender();?> <?php foreach ($options as $option):?> - <option value="<?php /* @escapeNotVerified */ echo $option->getValue() ?>"<?php if ($option->getValue() == $value) echo ' selected="selected"' ?>><?php /* @escapeNotVerified */ echo $option->getLabel() ?></option> + <option value="<?php /* @escapeNotVerified */ echo $option->getValue() ?>"<?php if ($option->getValue() == $value) echo ' selected="selected"' ?>><?php /* @escapeNotVerified */ echo __($option->getLabel()) ?></option> <?php endforeach;?> </select> </div> diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index 726fa0816d71d93c4d95a90f44a981dc3b628556..92de6ddcdbae908bcf8fdc353faac2898a2d3396 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -49,9 +49,10 @@ define([ }); return result; }, - getFromServer: function (sectionNames) { + getFromServer: function (sectionNames, updateSectionId) { sectionNames = sectionConfig.filterClientSideSections(sectionNames); var parameters = _.isArray(sectionNames) ? {sections: sectionNames.join(',')} : []; + parameters['update_section_id'] = updateSectionId; return $.getJSON(options.sectionLoadUrl, parameters).fail(function(jqXHR) { throw new Error(jqXHR); }); @@ -90,11 +91,16 @@ define([ this.data[sectionName](sectionData); }, update: function (sections) { + var sectionId = 0; + var sectionDataIds = $.cookieStorage.get('section_data_ids') || {}; _.each(sections, function (sectionData, sectionName) { + sectionId = sectionData['data_id']; + sectionDataIds[sectionName] = sectionId; storage.set(sectionName, sectionData); storageInvalidation.remove(sectionName); buffer.notify(sectionName, sectionData); }); + $.cookieStorage.set('section_data_ids', sectionDataIds); }, remove: function (sections) { _.each(sections, function (sectionName) { @@ -107,16 +113,49 @@ define([ var customerData = { init: function() { if (_.isEmpty(storage.keys())) { - this.reload(); + this.reload([], false); + } else if (this.needReload()) { + this.reload(this.getExpiredKeys(), false); } else { _.each(dataProvider.getFromStorage(storage.keys()), function (sectionData, sectionName) { buffer.notify(sectionName, sectionData); }); if (!_.isEmpty(storageInvalidation.keys())) { - this.reload(storageInvalidation.keys()); + this.reload(storageInvalidation.keys(), false); } } }, + needReload: function () { + var cookieSections = $.cookieStorage.get('section_data_ids'); + if (typeof cookieSections != 'object') { + return true; + } + var storageVal, name; + for (name in cookieSections) { + if (undefined !== name) { + storageVal = storage.get(name); + if (typeof storageVal == 'object' && cookieSections[name] > storageVal['data_id']) { + return true; + } + } + } + return false; + }, + getExpiredKeys: function() { + var cookieSections = $.cookieStorage.get('section_data_ids'); + + if (typeof cookieSections != 'object') { + return []; + } + var storageVal, name, expiredKeys = []; + for (name in cookieSections) { + storageVal = storage.get(name); + if (typeof storageVal == 'object' && cookieSections[name] != storage.get(name)['data_id']) { + expiredKeys.push(name); + } + } + return expiredKeys; + }, get: function (sectionName) { return buffer.get(sectionName); }, @@ -125,8 +164,8 @@ define([ data[sectionName] = sectionData; buffer.update(data); }, - reload: function (sectionNames) { - return dataProvider.getFromServer(sectionNames).done(function (sections) { + reload: function (sectionNames, updateSectionId) { + return dataProvider.getFromServer(sectionNames, updateSectionId).done(function (sections) { buffer.update(sections); }); }, @@ -151,7 +190,7 @@ define([ if (_.isObject(xhr.responseJSON) && !_.isEmpty(_.pick(xhr.responseJSON, redirects))) { return ; } - customerData.reload(sections); + customerData.reload(sections, true); } } }); diff --git a/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js b/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js index dfef07a4b87af5178c94fb8dee7c217253eb1e2d..324b0711f6b0381c7d323d3b9f1f9d0c25cc1996 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js +++ b/app/code/Magento/Customer/view/frontend/web/js/view/authentication-popup.js @@ -39,7 +39,9 @@ define( /** Init popup login window */ setModalElement: function (element) { - authenticationPopup.createPopUp(element); + if (authenticationPopup.modalWindow == null) { + authenticationPopup.createPopUp(element); + } }, /** Is login form enabled for current customer */ diff --git a/app/code/Magento/Deploy/Model/Deployer.php b/app/code/Magento/Deploy/Model/Deployer.php index 7337a9f0896e5299efa3f45b6d1f7a04b74b6386..cada5a4549aee90b4f18f6df5a2588dd37d82c25 100644 --- a/app/code/Magento/Deploy/Model/Deployer.php +++ b/app/code/Magento/Deploy/Model/Deployer.php @@ -118,21 +118,23 @@ class Deployer /** @var \Magento\Theme\Model\View\Design $design */ $design = $this->objectManager->create('Magento\Theme\Model\View\Design'); $design->setDesignTheme($themePath, $area); + $assetRepo = $this->objectManager->create( + 'Magento\Framework\View\Asset\Repository', + [ + 'design' => $design, + ] + ); $fileManager = $this->objectManager->create( 'Magento\RequireJs\Model\FileManager', [ 'config' => $this->objectManager->create( 'Magento\Framework\RequireJs\Config', [ - 'assetRepo' => $this->objectManager->create( - 'Magento\Framework\View\Asset\Repository', - [ - 'design' => $design, - ] - ), + 'assetRepo' => $assetRepo, 'design' => $design, ] ), + 'assetRepo' => $assetRepo, ] ); $fileManager->createRequireJsConfigAsset(); @@ -161,6 +163,7 @@ class Deployer null ); } + $fileManager->clearBundleJsPool(); $this->bundleManager->flush(); $this->output->writeln("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n"); } diff --git a/app/code/Magento/Developer/Model/TemplateEngine/Plugin/DebugHints.php b/app/code/Magento/Developer/Model/TemplateEngine/Plugin/DebugHints.php index 33f9eea54015be86a303b36ce309b83340cd7331..b23ba2f88779d3be265a2dc489d9951b3dc56154 100644 --- a/app/code/Magento/Developer/Model/TemplateEngine/Plugin/DebugHints.php +++ b/app/code/Magento/Developer/Model/TemplateEngine/Plugin/DebugHints.php @@ -5,77 +5,100 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ - -// @codingStandardsIgnoreFile - namespace Magento\Developer\Model\TemplateEngine\Plugin; +use Magento\Developer\Helper\Data as DevHelper; +use Magento\Developer\Model\TemplateEngine\Decorator\DebugHintsFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\View\TemplateEngineFactory; +use Magento\Framework\View\TemplateEngineInterface; use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; class DebugHints { - /**#@+ - * XPath of configuration of the debugging hints + /** + * XPath of configuration of the debug block names */ - const XML_PATH_DEBUG_TEMPLATE_HINTS = 'dev/debug/template_hints'; - const XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS = 'dev/debug/template_hints_blocks'; - /**#@-*/ + /** + * @var ScopeConfigInterface + */ + protected $scopeConfig; + + /** + * @var StoreManagerInterface + */ + protected $storeManager; /** - * @var \Magento\Framework\ObjectManagerInterface + * @var DevHelper */ - private $objectManager; + protected $devHelper; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var DebugHintsFactory */ - private $scopeConfig; + protected $debugHintsFactory; /** - * @var \Magento\Developer\Helper\Data + * XPath of configuration of the debug hints + * + * Allowed values: + * dev/debug/template_hints_storefront + * dev/debug/template_hints_admin + * + * @var string */ - private $devHelper; + protected $debugHintsPath; /** - * @param \Magento\Framework\ObjectManagerInterface $objectManager - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Developer\Helper\Data $devHelper + * @param ScopeConfigInterface $scopeConfig + * @param StoreManagerInterface $storeManager + * @param DevHelper $devHelper + * @param DebugHintsFactory $debugHintsFactory + * @param string $debugHintsPath */ public function __construct( - \Magento\Framework\ObjectManagerInterface $objectManager, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Developer\Helper\Data $devHelper + ScopeConfigInterface $scopeConfig, + StoreManagerInterface $storeManager, + DevHelper $devHelper, + DebugHintsFactory $debugHintsFactory, + $debugHintsPath ) { - $this->objectManager = $objectManager; $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; $this->devHelper = $devHelper; + $this->debugHintsFactory = $debugHintsFactory; + $this->debugHintsPath = $debugHintsPath; } /** * Wrap template engine instance with the debugging hints decorator, depending of the store configuration * - * @param \Magento\Framework\View\TemplateEngineFactory $subject - * @param \Magento\Framework\View\TemplateEngineInterface $invocationResult + * @param TemplateEngineFactory $subject + * @param TemplateEngineInterface $invocationResult * - * @return \Magento\Framework\View\TemplateEngineInterface + * @return TemplateEngineInterface * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterCreate( - \Magento\Framework\View\TemplateEngineFactory $subject, - \Magento\Framework\View\TemplateEngineInterface $invocationResult + TemplateEngineFactory $subject, + TemplateEngineInterface $invocationResult ) { - if ($this->scopeConfig->getValue(self::XML_PATH_DEBUG_TEMPLATE_HINTS, ScopeInterface::SCOPE_STORE) && - $this->devHelper->isDevAllowed()) { + $storeCode = $this->storeManager->getStore()->getCode(); + if ($this->scopeConfig->getValue($this->debugHintsPath, ScopeInterface::SCOPE_STORE, $storeCode) + && $this->devHelper->isDevAllowed()) { $showBlockHints = $this->scopeConfig->getValue( self::XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS, - ScopeInterface::SCOPE_STORE - ); - return $this->objectManager->create( - 'Magento\Developer\Model\TemplateEngine\Decorator\DebugHints', - ['subject' => $invocationResult, 'showBlockHints' => $showBlockHints] + ScopeInterface::SCOPE_STORE, + $storeCode ); + return $this->debugHintsFactory->create([ + 'subject' => $invocationResult, + 'showBlockHints' => $showBlockHints, + ]); } return $invocationResult; } diff --git a/app/code/Magento/Developer/Test/Unit/Model/TemplateEngine/Plugin/DebugHintsTest.php b/app/code/Magento/Developer/Test/Unit/Model/TemplateEngine/Plugin/DebugHintsTest.php index 00cc776db30eb9e2195366971f391957be16f2a1..9ccd6cb99dd3ce194ec35007c2c11932a9eb52a4 100644 --- a/app/code/Magento/Developer/Test/Unit/Model/TemplateEngine/Plugin/DebugHintsTest.php +++ b/app/code/Magento/Developer/Test/Unit/Model/TemplateEngine/Plugin/DebugHintsTest.php @@ -5,130 +5,194 @@ */ namespace Magento\Developer\Test\Unit\Model\TemplateEngine\Plugin; +use Magento\Developer\Model\TemplateEngine\Decorator\DebugHintsFactory; use Magento\Developer\Model\TemplateEngine\Plugin\DebugHints; +use Magento\Store\Model\ScopeInterface; class DebugHintsTest extends \PHPUnit_Framework_TestCase { /** - * @var DebugHints + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $model; + protected $scopeConfigMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectManagerMock; + protected $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Developer\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfigMock; + protected $devHelperMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var DebugHintsFactory | \PHPUnit_Framework_MockObject_MockObject */ - protected $devHelperMock; + protected $debugHintsFactory; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @return void */ - protected $subjectMock; - protected function setUp() { - $this->objectManagerMock = $this->getMock('Magento\Framework\ObjectManagerInterface'); - $this->scopeConfigMock = $this->getMock('Magento\Framework\App\Config\ScopeConfigInterface'); - $this->devHelperMock = $this->getMock('Magento\Developer\Helper\Data', [], [], '', false); - $this->subjectMock = $this->getMock( - 'Magento\Framework\View\TemplateEngineFactory', - [], - [], - '', - false - ); - $this->model = new \Magento\Developer\Model\TemplateEngine\Plugin\DebugHints($this->objectManagerMock, $this->scopeConfigMock, $this->devHelperMock); + $this->scopeConfigMock = $this->getMockBuilder('Magento\Framework\App\Config\ScopeConfigInterface') + ->getMockForAbstractClass(); + + $this->storeManager = $this->getMockBuilder('Magento\Store\Model\StoreManagerInterface') + ->getMockForAbstractClass(); + + $this->devHelperMock = $this->getMockBuilder('Magento\Developer\Helper\Data') + ->disableOriginalConstructor() + ->getMock(); + + $this->debugHintsFactory = $this->getMockBuilder( + 'Magento\Developer\Model\TemplateEngine\Decorator\DebugHintsFactory' + ) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); } /** + * @param string $debugHintsPath * @param bool $showBlockHints + * @return void * @dataProvider afterCreateActiveDataProvider */ - public function testAfterCreateActive($showBlockHints) + public function testAfterCreateActive($debugHintsPath, $showBlockHints) { - $this->devHelperMock->expects($this->once())->method('isDevAllowed')->will($this->returnValue(true)); - $this->_setupConfigFixture(true, $showBlockHints); + $this->devHelperMock->expects($this->once()) + ->method('isDevAllowed') + ->willReturn(true); + + $this->setupConfigFixture($debugHintsPath, true, $showBlockHints); + $engine = $this->getMock('Magento\Framework\View\TemplateEngineInterface'); - $engineDecorated = $this->getMock('Magento\Framework\View\TemplateEngineInterface'); - $this->objectManagerMock->expects( - $this->once() - )->method( - 'create' - )->with( - 'Magento\Developer\Model\TemplateEngine\Decorator\DebugHints', - $this->identicalTo(['subject' => $engine, 'showBlockHints' => $showBlockHints]) - )->will( - $this->returnValue($engineDecorated) + + $debugHintsDecorator = $this->getMockBuilder( + 'Magento\Developer\Model\TemplateEngine\Decorator\DebugHints' + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->debugHintsFactory->expects($this->once()) + ->method('create') + ->with([ + 'subject' => $engine, + 'showBlockHints' => $showBlockHints, + ]) + ->willReturn($debugHintsDecorator); + + $subjectMock = $this->getMockBuilder('Magento\Framework\View\TemplateEngineFactory') + ->disableOriginalConstructor() + ->getMock(); + + $debugHints = new DebugHints( + $this->scopeConfigMock, + $this->storeManager, + $this->devHelperMock, + $this->debugHintsFactory, + $debugHintsPath ); - $this->assertEquals($engineDecorated, $this->model->afterCreate($this->subjectMock, $engine)); + + $this->assertEquals($debugHintsDecorator, $debugHints->afterCreate($subjectMock, $engine)); } + /** + * @return array + */ public function afterCreateActiveDataProvider() { - return ['block hints disabled' => [false], 'block hints enabled' => [true]]; + return [ + ['dev/debug/template_hints_storefront', false], + ['dev/debug/template_hints_storefront', true], + ['dev/debug/template_hints_admin', false], + ['dev/debug/template_hints_admin', true], + ]; } /** + * @param string $debugHintsPath * @param bool $isDevAllowed * @param bool $showTemplateHints + * @return void * @dataProvider afterCreateInactiveDataProvider */ - public function testAfterCreateInactive($isDevAllowed, $showTemplateHints) + public function testAfterCreateInactive($debugHintsPath, $isDevAllowed, $showTemplateHints) { - $this->devHelperMock->expects($this->any())->method('isDevAllowed')->will($this->returnValue($isDevAllowed)); - $this->_setupConfigFixture($showTemplateHints, true); - $this->objectManagerMock->expects($this->never())->method('create'); - $engine = $this->getMock('Magento\Framework\View\TemplateEngineInterface', [], [], '', false); - $this->assertSame($engine, $this->model->afterCreate($this->subjectMock, $engine)); + $this->devHelperMock->expects($this->any()) + ->method('isDevAllowed') + ->willReturn($isDevAllowed); + + $this->setupConfigFixture($debugHintsPath, $showTemplateHints, true); + + $engine = $this->getMock('Magento\Framework\View\TemplateEngineInterface'); + + $subjectMock = $this->getMockBuilder('Magento\Framework\View\TemplateEngineFactory') + ->disableOriginalConstructor() + ->getMock(); + + $debugHints = new DebugHints( + $this->scopeConfigMock, + $this->storeManager, + $this->devHelperMock, + $this->debugHintsFactory, + $debugHintsPath + ); + + $this->assertSame($engine, $debugHints->afterCreate($subjectMock, $engine)); } + /** + * @return array + */ public function afterCreateInactiveDataProvider() { return [ - 'dev disabled, template hints disabled' => [false, false], - 'dev disabled, template hints enabled' => [false, true], - 'dev enabled, template hints disabled' => [true, false] + ['dev/debug/template_hints_storefront', false, false], + ['dev/debug/template_hints_storefront', false, true], + ['dev/debug/template_hints_storefront', true, false], + ['dev/debug/template_hints_admin', false, false], + ['dev/debug/template_hints_admin', false, true], + ['dev/debug/template_hints_admin', true, false], ]; } /** * Setup fixture values for store config * + * @param string $debugHintsPath * @param bool $showTemplateHints * @param bool $showBlockHints + * @return void */ - protected function _setupConfigFixture($showTemplateHints, $showBlockHints) + protected function setupConfigFixture($debugHintsPath, $showTemplateHints, $showBlockHints) { - $this->scopeConfigMock->expects( - $this->atLeastOnce() - )->method( - 'getValue' - )->will( - $this->returnValueMap( + $storeCode = 'default'; + $storeMock = $this->getMock('Magento\Store\Api\Data\StoreInterface'); + $storeMock->expects($this->once()) + ->method('getCode') + ->willReturn($storeCode); + $this->storeManager->expects($this->once()) + ->method('getStore') + ->willReturn($storeMock); + + $this->scopeConfigMock->expects($this->atLeastOnce()) + ->method('getValue') + ->willReturnMap([ [ - [ - DebugHints::XML_PATH_DEBUG_TEMPLATE_HINTS, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - $showTemplateHints, - ], - [ - DebugHints::XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - $showBlockHints - ], + $debugHintsPath, + ScopeInterface::SCOPE_STORE, + $storeCode, + $showTemplateHints, + ], + [ + DebugHints::XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS, + ScopeInterface::SCOPE_STORE, + $storeCode, + $showBlockHints ] - ) - ); + ]); } } diff --git a/app/code/Magento/Developer/etc/adminhtml/di.xml b/app/code/Magento/Developer/etc/adminhtml/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..ff22a95f0e92c698ff6d80185c0f5e505fca1e32 --- /dev/null +++ b/app/code/Magento/Developer/etc/adminhtml/di.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\View\TemplateEngineFactory"> + <plugin name="debug_hints" type="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints" sortOrder="10"/> + </type> + <type name="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints"> + <arguments> + <argument name="debugHintsPath" xsi:type="string">dev/debug/template_hints_admin</argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Developer/etc/di.xml b/app/code/Magento/Developer/etc/di.xml index ea0f67f38bed507d85c70467b78457555a012136..436385046bc060fdce294221979dae5a5f6383a4 100644 --- a/app/code/Magento/Developer/etc/di.xml +++ b/app/code/Magento/Developer/etc/di.xml @@ -10,9 +10,6 @@ <preference for="Magento\Framework\Css\PreProcessor\ErrorHandlerInterface" type="Magento\Framework\Css\PreProcessor\ErrorHandler" /> <preference for="Magento\Framework\Css\PreProcessor\AdapterInterface" type="Magento\Framework\Css\PreProcessor\Adapter\Less\Oyejorge" /> - <type name="Magento\Framework\View\TemplateEngineFactory"> - <plugin name="debug_hints" type="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints" sortOrder="10"/> - </type> <type name="Magento\Framework\View\Result\Page"> <arguments> <argument name="pageConfigRendererFactory" xsi:type="object">Magento\Developer\Model\View\Page\Config\RendererFactory</argument> diff --git a/app/code/Magento/Developer/etc/frontend/di.xml b/app/code/Magento/Developer/etc/frontend/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..3fd5f545b9d04f1f9c68f9b2b969984c9ffaefba --- /dev/null +++ b/app/code/Magento/Developer/etc/frontend/di.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\View\TemplateEngineFactory"> + <plugin name="debug_hints" type="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints" sortOrder="10"/> + </type> + <type name="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints"> + <arguments> + <argument name="debugHintsPath" xsi:type="string">dev/debug/template_hints_storefront</argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Dhl/composer.json b/app/code/Magento/Dhl/composer.json index 033291581ecafaa67d0cb60d76d38aa6f75b3c85..1f577af21d55cb1100238b9b8ce5a9bfc43ab029 100644 --- a/app/code/Magento/Dhl/composer.json +++ b/app/code/Magento/Dhl/composer.json @@ -9,7 +9,6 @@ "magento/module-backend": "1.0.0-beta", "magento/module-directory": "1.0.0-beta", "magento/module-sales": "1.0.0-beta", - "magento/module-checkout": "1.0.0-beta", "magento/module-catalog": "1.0.0-beta", "magento/module-catalog-inventory": "1.0.0-beta", "magento/module-quote": "1.0.0-beta", @@ -17,6 +16,9 @@ "lib-libxml": "*", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-checkout": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/Directory/Helper/Data.php b/app/code/Magento/Directory/Helper/Data.php index 00e8ca5fb2a541e0b342904cf31bc43f96216b9e..a247903fdf1649387ceea3f540362762aaa4e1ad 100644 --- a/app/code/Magento/Directory/Helper/Data.php +++ b/app/code/Magento/Directory/Helper/Data.php @@ -34,6 +34,12 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper const XML_PATH_DEFAULT_TIMEZONE = 'general/locale/timezone'; /**#@-*/ + /** + * Path to config value that contains codes of the most used countries. + * Such countries can be shown on the top of the country list. + */ + const XML_PATH_TOP_COUNTRIES = 'general/country/destinations'; + /** * Country collection * @@ -336,4 +342,18 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper } return $regions; } + + /** + * Retrieve list of codes of the most used countries + * + * @return array + */ + public function getTopCountryCodes() + { + $configValue = (string)$this->scopeConfig->getValue( + self::XML_PATH_TOP_COUNTRIES, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + return !empty($configValue) ? explode(',', $configValue) : []; + } } diff --git a/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php b/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php index e5eacfbe48a726b26685d51687d29d1ac7f9b0cf..21eb07942bfc01d613002939af4b383cd6eb2167 100644 --- a/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Directory/Test/Unit/Helper/DataTest.php @@ -259,4 +259,31 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->_object->getCountryCollection($store); } + + /** + * @param string $topCountriesValue + * @param array $expectedResult + * @dataProvider topCountriesDataProvider + */ + public function testGetTopCountryCodesReturnsParsedConfigurationValue($topCountriesValue, $expectedResult) + { + $this->scopeConfigMock->expects($this->once()) + ->method('getValue')->with(\Magento\Directory\Helper\Data::XML_PATH_TOP_COUNTRIES) + ->willReturn($topCountriesValue); + + $this->assertEquals($expectedResult, $this->_object->getTopCountryCodes()); + } + + /** + * @return array + */ + public function topCountriesDataProvider() + { + return [ + [null, []], + ['', []], + ['US', ['US']], + ['US,RU', ['US', 'RU']], + ]; + } } diff --git a/app/code/Magento/Downloadable/Api/Data/DownloadableOptionInterface.php b/app/code/Magento/Downloadable/Api/Data/DownloadableOptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..cbcf61d1e09b9db265848afe66864acc80f21263 --- /dev/null +++ b/app/code/Magento/Downloadable/Api/Data/DownloadableOptionInterface.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Downloadable\Api\Data; + +/** + * Downloadable Option + */ +interface DownloadableOptionInterface +{ + const DOWNLOADABLE_LINKS = 'downloadable_links'; + + /** + * Returns the list of downloadable links + * + * @return int[] + */ + public function getDownloadableLinks(); + + /** + * Sets the list of downloadable links + * + * @param int[] $downloadableLinks + * @return $this + */ + public function setDownloadableLinks($downloadableLinks); +} diff --git a/app/code/Magento/Downloadable/Api/LinkRepositoryInterface.php b/app/code/Magento/Downloadable/Api/LinkRepositoryInterface.php index 12b8458cecaecc6d2bf01f8f2fca96ceddd9c1af..4e02a870959cff195c34e1fd45757d3995675d42 100644 --- a/app/code/Magento/Downloadable/Api/LinkRepositoryInterface.php +++ b/app/code/Magento/Downloadable/Api/LinkRepositoryInterface.php @@ -37,7 +37,7 @@ interface LinkRepositoryInterface * @param bool $isGlobalScopeContent * @return int */ - public function save($sku, LinkInterface $link, $isGlobalScopeContent = false); + public function save($sku, LinkInterface $link, $isGlobalScopeContent = true); /** * Delete downloadable link diff --git a/app/code/Magento/Downloadable/Api/SampleRepositoryInterface.php b/app/code/Magento/Downloadable/Api/SampleRepositoryInterface.php index 8aa184795bb3fc526b87ec066bb9eaf2bf4fdeb7..1be3d641ec1b22d5b62f968f7cf2e2c5098f1917 100644 --- a/app/code/Magento/Downloadable/Api/SampleRepositoryInterface.php +++ b/app/code/Magento/Downloadable/Api/SampleRepositoryInterface.php @@ -40,7 +40,7 @@ interface SampleRepositoryInterface public function save( $sku, SampleInterface $sample, - $isGlobalScopeContent = false + $isGlobalScopeContent = true ); /** diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php index 1436fe674366f4ed0044c6ed66ac8ded44853e28..1ad7488cc32caa34ed76b518873a7df9be988c87 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php @@ -5,12 +5,18 @@ */ namespace Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab; +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Block\Widget; +use Magento\Backend\Block\Widget\Tab\TabInterface; +use Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs; +use Magento\Framework\Registry; + /** * Adminhtml catalog product downloadable items tab and form * * @author Magento Core Team <core@magentocommerce.com> */ -class Downloadable extends \Magento\Backend\Block\Widget implements \Magento\Backend\Block\Widget\Tab\TabInterface +class Downloadable extends Widget implements TabInterface { /** * Reference to product objects that is being edited @@ -34,29 +40,39 @@ class Downloadable extends \Magento\Backend\Block\Widget implements \Magento\Bac * * @var string */ - protected $accordionBlockId = 'downloadableInfo'; + protected $blockId = 'downloadableInfo'; /** * Core registry * - * @var \Magento\Framework\Registry + * @var Registry */ protected $_coreRegistry = null; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Framework\Registry $registry + * @param Context $context + * @param Registry $registry * @param array $data */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Framework\Registry $registry, + Context $context, + Registry $registry, array $data = [] ) { $this->_coreRegistry = $registry; parent::__construct($context, $data); } + /** + * Get parent tab code + * + * @return string + */ + public function getParentTab() + { + return 'product-details'; + } + /** * Check is readonly block * @@ -122,7 +138,7 @@ class Downloadable extends \Magento\Backend\Block\Widget implements \Magento\Bac */ public function getGroupCode() { - return \Magento\Catalog\Block\Adminhtml\Product\Edit\Tabs::ADVANCED_TAB_GROUP_CODE; + return Tabs::ADVANCED_TAB_GROUP_CODE; } /** @@ -132,43 +148,23 @@ class Downloadable extends \Magento\Backend\Block\Widget implements \Magento\Bac */ public function getContentTabId() { - return 'tab_content_' . $this->accordionBlockId; + return 'tab_content_' . $this->blockId; } /** - * Render block HTML - * - * @return string + * @return bool + */ + public function isDownloadable() + { + return $this->getProduct()->getTypeId() == \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE; + } + + /** + * @return $this */ - protected function _toHtml() + protected function _prepareLayout() { - $accordion = $this->getLayout()->createBlock('Magento\Backend\Block\Widget\Accordion') - ->setId($this->accordionBlockId); - $accordion->addItem( - 'samples', - [ - 'title' => __('Samples'), - 'content' => $this->getLayout()->createBlock( - 'Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Samples' - )->toHtml(), - 'open' => false - ] - ); - - $accordion->addItem( - 'links', - [ - 'title' => __('Links'), - 'content' => $this->getLayout()->createBlock( - 'Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Links', - 'catalog.product.edit.tab.downloadable.links' - )->toHtml(), - 'open' => true - ] - ); - - $this->setChild('accordion', $accordion); - - return parent::_toHtml(); + $this->setData('opened', $this->isDownloadable()); + return parent::_prepareLayout(); } } diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php index 224233a90353d1be4a6bd3830272789a890f0a5e..4b3c95f89150c652b3bf6bab773ee262b30dbcc8 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php @@ -191,9 +191,9 @@ class Links extends \Magento\Backend\Block\Template 'Magento\Backend\Block\Widget\Button' )->setData( [ - 'label' => __('Add New Row'), + 'label' => __('Add New Link'), 'id' => 'add_link_item', - 'class' => 'add', + 'class' => 'action-add', 'data_attribute' => ['action' => 'add-link'], ] ); diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php index ce2e80e7d335afc730b1ee8af47fc9c31a4c3942..f4b5875ffe29e2a72a582832e0c7a554f4f55491 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php @@ -121,9 +121,9 @@ class Samples extends \Magento\Backend\Block\Widget 'Magento\Backend\Block\Widget\Button' )->setData( [ - 'label' => __('Add New Row'), + 'label' => __('Add New Link'), 'id' => 'add_sample_item', - 'class' => 'add', + 'class' => 'action-add', 'data_attribute' => ['action' => 'add-sample'], ] ); diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/SuggestProductTemplates.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/SuggestProductTemplates.php deleted file mode 100644 index b605ecca53490d96c68e65c80939c93e9378dfcc..0000000000000000000000000000000000000000 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/SuggestProductTemplates.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; - -class SuggestProductTemplates extends \Magento\Catalog\Controller\Adminhtml\Product\SuggestProductTemplates -{ -} diff --git a/app/code/Magento/Downloadable/Model/DownloadableOption.php b/app/code/Magento/Downloadable/Model/DownloadableOption.php new file mode 100644 index 0000000000000000000000000000000000000000..b7c73bb141587c2ec500805cb93d57900258c260 --- /dev/null +++ b/app/code/Magento/Downloadable/Model/DownloadableOption.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Downloadable\Model; + +use Magento\Downloadable\Api\Data\DownloadableOptionInterface; +use Magento\Framework\Model\AbstractExtensibleModel; + +/** + * @codeCoverageIgnore + */ +class DownloadableOption extends AbstractExtensibleModel implements DownloadableOptionInterface +{ + /** + * Returns the list of downloadable links + * + * @return int[] + */ + public function getDownloadableLinks() + { + return $this->getData(self::DOWNLOADABLE_LINKS); + } + + /** + * Sets the list of downloadable links + * + * @param int[] $downloadableLinks + * @return $this + */ + public function setDownloadableLinks($downloadableLinks) + { + return $this->setData(self::DOWNLOADABLE_LINKS, $downloadableLinks); + } + + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Downloadable\Api\Data\DownloadableOptionExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * Set an extension attributes object. + * + * @param \Magento\Downloadable\Api\Data\DownloadableOptionExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Downloadable\Api\Data\DownloadableOptionExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Downloadable/Model/LinkRepository.php b/app/code/Magento/Downloadable/Model/LinkRepository.php index 6f047f4b0a837b129ce504eec45b86fcd1cf1fe1..1501a3754c0abb08216469524ff4cecae7e55f23 100644 --- a/app/code/Magento/Downloadable/Model/LinkRepository.php +++ b/app/code/Magento/Downloadable/Model/LinkRepository.php @@ -148,7 +148,7 @@ class LinkRepository implements \Magento\Downloadable\Api\LinkRepositoryInterfac * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function save($sku, LinkInterface $link, $isGlobalScopeContent = false) + public function save($sku, LinkInterface $link, $isGlobalScopeContent = true) { $product = $this->productRepository->get($sku, true); if ($link->getId() !== null) { diff --git a/app/code/Magento/Downloadable/Model/Product/TypeTransitionManager/Plugin/Downloadable.php b/app/code/Magento/Downloadable/Model/Product/TypeTransitionManager/Plugin/Downloadable.php index 19cc68b641ac5e5d74970cc3b106ddd869c216af..d622fa745dfeefe13ad133d25730d285da142241 100644 --- a/app/code/Magento/Downloadable/Model/Product/TypeTransitionManager/Plugin/Downloadable.php +++ b/app/code/Magento/Downloadable/Model/Product/TypeTransitionManager/Plugin/Downloadable.php @@ -7,6 +7,7 @@ namespace Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin; use Closure; use Magento\Framework\App\RequestInterface; +use Magento\Catalog\Model\Product\Edit\WeightResolver; /** * Plugin for product type transition manager @@ -20,12 +21,19 @@ class Downloadable */ protected $request; + /** + * @var \Magento\Catalog\Model\Product\Edit\WeightResolver + */ + protected $weightResolver; + /** * @param RequestInterface $request + * @param WeightResolver $weightResolver */ - public function __construct(RequestInterface $request) + public function __construct(RequestInterface $request, WeightResolver $weightResolver) { $this->request = $request; + $this->weightResolver = $weightResolver; } /** @@ -62,7 +70,7 @@ class Downloadable } } } - if ($isTypeCompatible && $hasDownloadableData && $product->hasIsVirtual()) { + if ($isTypeCompatible && $hasDownloadableData && !$this->weightResolver->resolveProductHasWeight($product)) { $product->setTypeId(\Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE); return; } diff --git a/app/code/Magento/Downloadable/Model/Quote/Item/CartItemProcessor.php b/app/code/Magento/Downloadable/Model/Quote/Item/CartItemProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..63db5a53e5d3fdc6799d5cdae713d1faf1da1d59 --- /dev/null +++ b/app/code/Magento/Downloadable/Model/Quote/Item/CartItemProcessor.php @@ -0,0 +1,116 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Downloadable\Model\Quote\Item; + +use Magento\Quote\Model\Quote\Item\CartItemProcessorInterface; +use Magento\Quote\Api\Data\CartItemInterface; +use Magento\Framework\DataObject\Factory as DataObjectFactory; + +class CartItemProcessor implements CartItemProcessorInterface +{ + /** + * @var DataObjectFactory + */ + private $objectFactory; + + /** + * @var \Magento\Framework\Api\DataObjectHelper + */ + private $dataObjectHelper; + + /** + * @var \Magento\Downloadable\Model\DownloadableOptionFactory + */ + private $downloadableOptionFactory; + + /** + * @var \Magento\Quote\Model\Quote\ProductOptionFactory + */ + private $productOptionFactory; + + /** + * @var \Magento\Quote\Api\Data\ProductOptionExtensionFactory + */ + private $extensionFactory; + + /** + * @param DataObjectFactory $objectFactory + * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + * @param \Magento\Downloadable\Model\DownloadableOptionFactory $downloadableOptionFactory + * @param \Magento\Quote\Model\Quote\ProductOptionFactory $productOptionFactory + * @param \Magento\Quote\Api\Data\ProductOptionExtensionFactory $extensionFactory + */ + public function __construct( + DataObjectFactory $objectFactory, + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + \Magento\Downloadable\Model\DownloadableOptionFactory $downloadableOptionFactory, + \Magento\Quote\Model\Quote\ProductOptionFactory $productOptionFactory, + \Magento\Quote\Api\Data\ProductOptionExtensionFactory $extensionFactory + ) { + $this->objectFactory = $objectFactory; + $this->dataObjectHelper = $dataObjectHelper; + $this->downloadableOptionFactory = $downloadableOptionFactory; + $this->productOptionFactory = $productOptionFactory; + $this->extensionFactory = $extensionFactory; + } + + /** + * {@inheritdoc} + */ + public function convertToBuyRequest(CartItemInterface $cartItem) + { + if ($cartItem->getProductOption() + && $cartItem->getProductOption()->getExtensionAttributes()->getDownloadableOption() + ) { + $downloadableLinks = $cartItem->getProductOption()->getExtensionAttributes()->getDownloadableOption() + ->getDownloadableLinks(); + if (!empty($downloadableLinks)) { + return $this->objectFactory->create([ + 'links' => $downloadableLinks, + ]); + } + } + return null; + } + + /** + * Process cart item product options + * + * @param CartItemInterface $cartItem + * @return CartItemInterface + */ + public function processProductOptions(CartItemInterface $cartItem) + { + $downloadableLinkIds = []; + $option = $cartItem->getOptionByCode('downloadable_link_ids'); + if (!empty($option)) { + $downloadableLinkIds = explode(',', $option->getValue()); + } + + $downloadableOption = $this->downloadableOptionFactory->create(); + $this->dataObjectHelper->populateWithArray( + $downloadableOption, + [ + 'downloadable_links' => $downloadableLinkIds + ], + 'Magento\Downloadable\Api\Data\DownloadableOptionInterface' + ); + + $productOption = ($cartItem->getProductOption()) + ? $cartItem->getProductOption() + : $this->productOptionFactory->create(); + + $extensibleAttribute = ($productOption->getExtensionAttributes()) + ? $productOption->getExtensionAttributes() + : $this->extensionFactory->create(); + + $extensibleAttribute->setDownloadableOption($downloadableOption); + $productOption->setExtensionAttributes($extensibleAttribute); + $cartItem->setProductOption($productOption); + + return $cartItem; + } +} diff --git a/app/code/Magento/Downloadable/Model/Resource/Indexer/Price.php b/app/code/Magento/Downloadable/Model/Resource/Indexer/Price.php index 83e240580415139aad4dee46666c17010c52d5fc..3a06503673745993f45d53f5bcf32324efd06783 100644 --- a/app/code/Magento/Downloadable/Model/Resource/Indexer/Price.php +++ b/app/code/Magento/Downloadable/Model/Resource/Indexer/Price.php @@ -133,7 +133,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul $connection->query($query); $ifTierPrice = $connection->getCheckSql('i.tier_price IS NOT NULL', '(i.tier_price + id.min_price)', 'NULL'); - $ifGroupPrice = $connection->getCheckSql('i.group_price IS NOT NULL', '(i.group_price + id.min_price)', 'NULL'); $select = $connection->select()->join( ['id' => $table], @@ -145,7 +144,6 @@ class Price extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defaul 'min_price' => new \Zend_Db_Expr('i.min_price + id.min_price'), 'max_price' => new \Zend_Db_Expr('i.max_price + id.max_price'), 'tier_price' => new \Zend_Db_Expr($ifTierPrice), - 'group_price' => new \Zend_Db_Expr($ifGroupPrice), ] ); diff --git a/app/code/Magento/Downloadable/Model/SampleRepository.php b/app/code/Magento/Downloadable/Model/SampleRepository.php index 71d76e05f40a7d018eaaf128ad2c30e25eceeb62..4abf1da7f73e41c012a4a9bc18c5d20728f795a3 100644 --- a/app/code/Magento/Downloadable/Model/SampleRepository.php +++ b/app/code/Magento/Downloadable/Model/SampleRepository.php @@ -153,7 +153,7 @@ class SampleRepository implements \Magento\Downloadable\Api\SampleRepositoryInte public function save( $sku, SampleInterface $sample, - $isGlobalScopeContent = false + $isGlobalScopeContent = true ) { $product = $this->productRepository->get($sku, true); diff --git a/app/code/Magento/Downloadable/Setup/InstallData.php b/app/code/Magento/Downloadable/Setup/InstallData.php index f6f376617e0fe3c391726adddacbd5ff09f9aa1f..ef18bb25f1d18b0402dfd1518cfc33005d5aef39 100644 --- a/app/code/Magento/Downloadable/Setup/InstallData.php +++ b/app/code/Magento/Downloadable/Setup/InstallData.php @@ -155,7 +155,6 @@ class InstallData implements InstallDataInterface 'minimal_price', 'cost', 'tier_price', - 'group_price', 'weight', ]; diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTransitionManager/Plugin/DownloadableTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTransitionManager/Plugin/DownloadableTest.php index 1ab99374fd6ad58f512a5507502f8954ccdc4068..b5d2cb5a57d6fd45911f31f28527907d6c819c67 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTransitionManager/Plugin/DownloadableTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTransitionManager/Plugin/DownloadableTest.php @@ -27,6 +27,11 @@ class DownloadableTest extends \PHPUnit_Framework_TestCase */ protected $model; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $weightResolver; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -35,17 +40,21 @@ class DownloadableTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->requestMock = $this->getMock('Magento\Framework\App\Request\Http', [], [], '', false); - $this->model = new \Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin\Downloadable($this->requestMock); $this->productMock = $this->getMock( 'Magento\Catalog\Model\Product', - ['hasIsVirtual', 'getTypeId', 'setTypeId', '__wakeup'], + ['getTypeId', 'setTypeId'], [], '', false ); + $this->weightResolver = $this->getMock('Magento\Catalog\Model\Product\Edit\WeightResolver'); $this->subjectMock = $this->getMock('Magento\Catalog\Model\Product\TypeTransitionManager', [], [], '', false); $this->closureMock = function () { }; + $this->model = new \Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin\Downloadable( + $this->requestMock, + $this->weightResolver + ); } /** @@ -58,7 +67,7 @@ class DownloadableTest extends \PHPUnit_Framework_TestCase ->method('getPost') ->with('downloadable') ->will($this->returnValue(['link' => [['is_delete' => '']]])); - $this->productMock->expects($this->any())->method('hasIsVirtual')->will($this->returnValue(true)); + $this->weightResolver->expects($this->any())->method('resolveProductHasWeight')->willReturn(false); $this->productMock->expects($this->once())->method('getTypeId')->will($this->returnValue($currentTypeId)); $this->productMock->expects($this->once()) ->method('setTypeId') @@ -80,13 +89,13 @@ class DownloadableTest extends \PHPUnit_Framework_TestCase } /** - * @param bool $isVirtual + * @param bool $hasWeight * @param string $currentTypeId * @param string|null $downloadableData * @dataProvider productThatCannotBeTransformedToDownloadableDataProvider */ public function testAroundProcessProductWithProductThatCannotBeTransformedToDownloadable( - $isVirtual, + $hasWeight, $currentTypeId, $downloadableData ) { @@ -94,7 +103,7 @@ class DownloadableTest extends \PHPUnit_Framework_TestCase ->method('getPost') ->with('downloadable') ->will($this->returnValue($downloadableData)); - $this->productMock->expects($this->any())->method('hasIsVirtual')->will($this->returnValue($isVirtual)); + $this->weightResolver->expects($this->any())->method('resolveProductHasWeight')->willReturn($hasWeight); $this->productMock->expects($this->once())->method('getTypeId')->will($this->returnValue($currentTypeId)); $this->productMock->expects($this->never())->method('setTypeId'); @@ -107,11 +116,11 @@ class DownloadableTest extends \PHPUnit_Framework_TestCase public function productThatCannotBeTransformedToDownloadableDataProvider() { return [ - [true, 'custom_product_type', ['link' => [['is_delete' => '']]]], - [false, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, null], + [false, 'custom_product_type', ['link' => [['is_delete' => '']]]], [true, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, null], - [false, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, ['link' => [['is_delete' => '']]]], - [true, \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE, ['link' => [['is_delete' => '1']]]] + [false, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, null], + [true, \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, ['link' => [['is_delete' => '']]]], + [false, \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE, ['link' => [['is_delete' => '1']]]] ]; } } diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9379e9a0bad4c95648f6bff5555de0ec753d51bd --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Unit/Model/Quote/Item/CartItemProcessorTest.php @@ -0,0 +1,249 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Downloadable\Test\Unit\Model\Quote\Item; + +use Magento\Downloadable\Model\Quote\Item\CartItemProcessor; + +class CartItemProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CartItemProcessor + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $objectFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $objectHelperMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $optionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $extensionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $downloadableOptionFactoryMock; + + protected function setUp() + { + $this->objectFactoryMock = $this->getMock( + '\Magento\Framework\DataObject\Factory', + ['create'], + [], + '', + false + ); + $this->optionFactoryMock = $this->getMock( + '\Magento\Quote\Model\Quote\ProductOptionFactory', + ['create'], + [], + '', + false + ); + $this->objectHelperMock = $this->getMock('\Magento\Framework\Api\DataObjectHelper', [], [], '', false); + $this->extensionFactoryMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtensionFactory', + ['create'], + [], + '', + false + ); + $this->downloadableOptionFactoryMock = $this->getMock( + '\Magento\Downloadable\Model\DownloadableOptionFactory', + ['create'], + [], + '', + false + ); + + $this->model = new CartItemProcessor( + $this->objectFactoryMock, + $this->objectHelperMock, + $this->downloadableOptionFactoryMock, + $this->optionFactoryMock, + $this->extensionFactoryMock + ); + } + + public function testConvertToBuyRequestReturnsNullIfItemDoesNotContainProductOption() + { + $cartItemMock = $this->getMock('\Magento\Quote\Api\Data\CartItemInterface'); + $this->assertNull($this->model->convertToBuyRequest($cartItemMock)); + } + + public function testConvertToBuyRequest() + { + $downloadableLinks = [1, 2]; + $itemQty = 1; + + $cartItemMock = $this->getMock( + '\Magento\Quote\Model\Quote\Item', + ['getProductOption', 'setProductOption', 'getOptionByCode', 'getQty'], + [], + '', + false + ); + $productOptionMock = $this->getMock('\Magento\Quote\Api\Data\ProductOptionInterface'); + + $cartItemMock->expects($this->any())->method('getProductOption')->willReturn($productOptionMock); + $cartItemMock->expects($this->any())->method('getQty')->willReturn($itemQty); + $extAttributesMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOption', + ['getDownloadableOption'], + [], + '', + false + ); + $productOptionMock->expects($this->any())->method('getExtensionAttributes')->willReturn($extAttributesMock); + + $downloadableOptionMock = $this->getMock('\Magento\Downloadable\Api\Data\DownloadableOptionInterface'); + $extAttributesMock->expects($this->any()) + ->method('getDownloadableOption') + ->willReturn($downloadableOptionMock); + + $downloadableOptionMock->expects($this->any())->method('getDownloadableLinks')->willReturn($downloadableLinks); + + $buyRequestData = [ + 'links' => $downloadableLinks, + ]; + $buyRequestMock = new \Magento\Framework\DataObject($buyRequestData); + $this->objectFactoryMock->expects($this->once()) + ->method('create') + ->with($buyRequestData) + ->willReturn($buyRequestMock); + + $this->assertEquals($buyRequestMock, $this->model->convertToBuyRequest($cartItemMock)); + } + + public function testProcessProductOptions() + { + $downloadableLinks = [1, 2]; + + $customOption = $this->getMock('\Magento\Catalog\Model\Product\Configuration\Item\Option', [], [], '', false); + $customOption->expects($this->once())->method('getValue')->willReturn(implode(',', $downloadableLinks)); + + $cartItemMock = $this->getMock( + '\Magento\Quote\Model\Quote\Item', + ['getProduct', 'getProductOption', 'setProductOption', 'getOptionByCode'], + [], + '', + false + ); + $cartItemMock->expects($this->once()) + ->method('getOptionByCode') + ->with('downloadable_link_ids') + ->willReturn($customOption); + + $cartItemMock->expects($this->any()) + ->method('getProductOption') + ->willReturn(null); + + $downloadableOptionMock = $this->getMock('\Magento\Downloadable\Api\Data\DownloadableOptionInterface'); + $this->downloadableOptionFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($downloadableOptionMock); + + $productOptionMock = $this->getMock('\Magento\Quote\Api\Data\ProductOptionInterface'); + $this->optionFactoryMock->expects($this->once())->method('create')->willReturn($productOptionMock); + $productOptionMock->expects($this->once())->method('getExtensionAttributes')->willReturn(null); + + $extAttributeMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtension', + ['setDownloadableOption'], + [], + '', + false + ); + + $this->objectHelperMock->expects($this->once())->method('populateWithArray')->with( + $downloadableOptionMock, + [ + 'downloadable_links' => $downloadableLinks + ], + 'Magento\Downloadable\Api\Data\DownloadableOptionInterface' + ); + + $this->extensionFactoryMock->expects($this->once())->method('create')->willReturn($extAttributeMock); + $extAttributeMock->expects($this->once()) + ->method('setDownloadableOption') + ->with($downloadableOptionMock); + $productOptionMock->expects($this->once()) + ->method('setExtensionAttributes') + ->with($extAttributeMock); + $cartItemMock->expects($this->once())->method('setProductOption')->with($productOptionMock); + + $this->assertEquals($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } + + public function testProcessProductOptionsWhenItemDoesNotHaveDownloadableLinks() + { + $downloadableLinks = []; + + $cartItemMock = $this->getMock( + '\Magento\Quote\Model\Quote\Item', + ['getProduct', 'getProductOption', 'setProductOption', 'getOptionByCode'], + [], + '', + false + ); + $cartItemMock->expects($this->once()) + ->method('getOptionByCode') + ->with('downloadable_link_ids'); + + $extAttributeMock = $this->getMock( + '\Magento\Quote\Api\Data\ProductOptionExtension', + ['setDownloadableOption'], + [], + '', + false + ); + $productOptionMock = $this->getMock('\Magento\Quote\Api\Data\ProductOptionInterface'); + $productOptionMock->expects($this->any()) + ->method('getExtensionAttributes') + ->willReturn($extAttributeMock); + $cartItemMock->expects($this->any()) + ->method('getProductOption') + ->willReturn($productOptionMock); + + $downloadableOptionMock = $this->getMock('\Magento\Downloadable\Api\Data\DownloadableOptionInterface'); + $this->downloadableOptionFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($downloadableOptionMock); + + $this->optionFactoryMock->expects($this->never())->method('create'); + $this->extensionFactoryMock->expects($this->never())->method('create'); + + $this->objectHelperMock->expects($this->once())->method('populateWithArray')->with( + $downloadableOptionMock, + [ + 'downloadable_links' => $downloadableLinks + ], + 'Magento\Downloadable\Api\Data\DownloadableOptionInterface' + ); + + $extAttributeMock->expects($this->once()) + ->method('setDownloadableOption') + ->with($downloadableOptionMock); + $productOptionMock->expects($this->once()) + ->method('setExtensionAttributes') + ->with($extAttributeMock); + $cartItemMock->expects($this->once())->method('setProductOption')->with($productOptionMock); + + $this->assertEquals($cartItemMock, $this->model->processProductOptions($cartItemMock)); + } +} diff --git a/app/code/Magento/Downloadable/etc/di.xml b/app/code/Magento/Downloadable/etc/di.xml index acb63729a578108f11e7b5f9d59c60d3d1e00745..a400d104749dfe9ec546dd8dd63eed93eb866240 100644 --- a/app/code/Magento/Downloadable/etc/di.xml +++ b/app/code/Magento/Downloadable/etc/di.xml @@ -11,11 +11,6 @@ <argument name="coreFileStorageDb" xsi:type="object">Magento\MediaStorage\Helper\File\Storage\Database\Proxy</argument> </arguments> </type> - <type name="Magento\Catalog\Helper\Product"> - <arguments> - <argument name="typeSwitcherLabel" xsi:type="string">Virtual / Downloadable</argument> - </arguments> - </type> <type name="Magento\Catalog\Model\Product\CartConfiguration"> <plugin name="Downloadable" type="Magento\Downloadable\Model\Product\CartConfiguration\Plugin\Downloadable" /> </type> @@ -64,6 +59,13 @@ <type name="Magento\Catalog\Api\ProductRepositoryInterface"> <plugin name="downloadableAroundSave" type="\Magento\Downloadable\Model\Plugin\AroundProductRepositorySave"/> </type> + <type name="Magento\Quote\Model\Quote\Item\Repository"> + <arguments> + <argument name="cartItemProcessors" xsi:type="array"> + <item name="downloadable" xsi:type="object">Magento\Downloadable\Model\Quote\Item\CartItemProcessor</item> + </argument> + </arguments> + </type> <preference for="\Magento\Downloadable\Api\LinkRepositoryInterface" type="\Magento\Downloadable\Model\LinkRepository" /> <preference for="\Magento\Downloadable\Api\SampleRepositoryInterface" type="\Magento\Downloadable\Model\SampleRepository" /> <preference for="\Magento\Downloadable\Api\Data\LinkInterface" type="\Magento\Downloadable\Model\Link" /> @@ -71,4 +73,5 @@ <preference for="\Magento\Downloadable\Api\Data\File\ContentInterface" type="\Magento\Downloadable\Model\File\Content" /> <preference for="\Magento\Downloadable\Api\Data\File\ContentUploaderInterface" type="\Magento\Downloadable\Model\File\ContentUploader" /> <preference for="\Magento\Downloadable\Model\Product\TypeHandler\TypeHandlerInterface" type="\Magento\Downloadable\Model\Product\TypeHandler\TypeHandler" /> + <preference for="\Magento\Downloadable\Api\Data\DownloadableOptionInterface" type="\Magento\Downloadable\Model\DownloadableOption" /> </config> diff --git a/app/code/Magento/Downloadable/etc/extension_attributes.xml b/app/code/Magento/Downloadable/etc/extension_attributes.xml index 66e38c2f8e83b4010dfb92ace4be43d32f8dc197..da726515e91ba72ded30089e3aabac52f7cafad0 100644 --- a/app/code/Magento/Downloadable/etc/extension_attributes.xml +++ b/app/code/Magento/Downloadable/etc/extension_attributes.xml @@ -10,4 +10,7 @@ <attribute code="downloadable_product_links" type="Magento\Downloadable\Api\Data\LinkInterface[]" /> <attribute code="downloadable_product_samples" type="Magento\Downloadable\Api\Data\SampleInterface[]" /> </extension_attributes> + <extension_attributes for="Magento\Quote\Api\Data\ProductOptionInterface"> + <attribute code="downloadable_option" type="Magento\Downloadable\Api\Data\DownloadableOptionInterface" /> + </extension_attributes> </config> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml index 91234c097f475a6e26b6cac1b83ad4a637e374bf..d1805ee4664957b564af9f10201aa00b8bbcf26a 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml @@ -12,9 +12,13 @@ <arguments> <argument name="excluded_panel" xsi:type="string">product_info_tabs_downloadable_items_content</argument> </arguments> + <block name="downloadable_items" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable"> + <block name="catalog.product.edit.tab.downloadable.links" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Links" /> + <block class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Samples" /> + </block> <action method="addTab"> <argument name="name" xsi:type="string">downloadable_items</argument> - <argument name="block" xsi:type="string">Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable</argument> + <argument name="block" xsi:type="string">downloadable_items</argument> </action> </referenceBlock> </body> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml index 136c8c3a07307f0d1131d52456f14204e73deba9..a5feddff7a3aed6353f8137cc3dd8bf5f2943a4c 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml @@ -8,9 +8,13 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="product_tabs"> + <block name="downloadable_items" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable"> + <block name="catalog.product.edit.tab.downloadable.links" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Links" /> + <block class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Samples" /> + </block> <action method="addTab"> <argument name="name" xsi:type="string">downloadable_items</argument> - <argument name="block" xsi:type="string">Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable</argument> + <argument name="block" xsi:type="string">downloadable_items</argument> </action> </referenceBlock> </body> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml index 136c8c3a07307f0d1131d52456f14204e73deba9..a5feddff7a3aed6353f8137cc3dd8bf5f2943a4c 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml @@ -8,9 +8,13 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="product_tabs"> + <block name="downloadable_items" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable"> + <block name="catalog.product.edit.tab.downloadable.links" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Links" /> + <block class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\Samples" /> + </block> <action method="addTab"> <argument name="name" xsi:type="string">downloadable_items</argument> - <argument name="block" xsi:type="string">Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable</argument> + <argument name="block" xsi:type="string">downloadable_items</argument> </action> </referenceBlock> </body> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml index 6892c319357bd2a0fd28139c9ca0f36784befce9..688d231547f437eaf906b38beca4503ea91ab729 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml @@ -10,7 +10,7 @@ <?php /** - * @see \Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable + * @var $block \Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable */ ?> <script> @@ -38,13 +38,64 @@ var uploaderTemplate = '<div class="no-display" id="[[idName]]-template">' + '</div>' + '<div class="no-display" id="[[idName]]-template-progress">' + '<%- data.percent %>% <%- data.uploaded %> / <%- data.total %>' + - '</div>'; - - var fileListTemplate = '<span class="file-info">' + + '</div>', + + fileListTemplate = '<span class="file-info">' + '<span class="file-info-name"><%= data.name %></span>' + ' ' + '<span class="file-info-size">(<%- data.size %>)</span>' + - '</span>'; + '</span>', + downloadableCheckbox = jQuery('[data-action=change-type-product]'), + toggleTab = function (showTab) { + showTab = showTab === undefined ? <?= /* @noEscape */ $block->isDownloadable() ? 'true' : 'false' ?> : showTab; + var tab = jQuery('[data-tab=<?= /* @noEscape */ $block->getTabId()?>]'); + if (tab.data('collapsible')) { + tab.collapsible(showTab ? 'activate' : 'deactivate'); + } else { + tab.on('collapsiblecreated', function() { + jQuery(this).collapsible(showTab ? 'activate' : 'deactivate'); + }); + } + }, + switchConfigurationSectionMessage = function (hasWeight) { + if (hasWeight) { + configurationSectionMessageHandler.change(false); + } else { + toggleTab(true); + configurationSectionMessageHandler.change(true); + } + }; + + var configurationSectionMessageHandler = (new function(attributeTitle, attributeTab) { + this.title = jQuery(attributeTitle); + this.buttons = jQuery('button', attributeTab); + this.newText = '<?= /* @noEscape */ __('Configurations cannot be created for a standard product with downloadable files.' + . ' To create configurations, first remove all downloadable files.');?>'; + this.oldText = this.title.text(); + this.change = function (change) { + if (change) { + this.title.text(this.newText); + this.buttons.hide(); + } else { + this.title.text(this.oldText); + this.buttons.show(); + } + }; + if (downloadableCheckbox.is(':checked')) { + this.title.text(this.newText); + this.buttons.hide(); + } + }('[data-role="product-create-configuration-info"]', '[data-tab="super_config"]')); + + downloadableCheckbox.on('change', function () { + switchConfigurationSectionMessage(!jQuery(this).is(':checked')); + jQuery(document).trigger('typeSwitcherOut'); + }); + + jQuery(document).on('typeSwitcher', function (event, data) { + downloadableCheckbox.attr('checked', !data.hasWeight); + switchConfigurationSectionMessage(data.hasWeight); + }); window.Downloadable = { uploaderObj : $H({}), @@ -128,8 +179,9 @@ var uploaderTemplate = '<div class="no-display" id="[[idName]]-template">' + }) } ); - if ($(this.idName+'_save')) { - $(this.idName+'_save').value = this.fileValue.toJSON + var elementSave = $(this.idName+'_save'); + if (elementSave) { + elementSave.value = this.fileValue.toJSON ? this.fileValue.toJSON() : Object.toJSON(this.fileValue); } @@ -141,7 +193,7 @@ var uploaderTemplate = '<div class="no-display" id="[[idName]]-template">' + new Downloadable.FileList(this.idName, null); <?php endif;?> } - } + }; Downloadable.FileList = Class.create(); Downloadable.FileList.prototype = { @@ -193,7 +245,7 @@ var uploaderTemplate = '<div class="no-display" id="[[idName]]-template">' + $(this.containerId + '-old').show(); }.bind(this)); } - } + }; var alertAlreadyDisplayed = false; @@ -207,25 +259,35 @@ var uploaderTemplate = '<div class="no-display" id="[[idName]]-template">' + }); </script> +<div data-tab-type="tab_content_downloadableInfo" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>_content" + <?php /* @escapeNotVerified */ echo $block->getUiId('tab', 'content', $block->getId()) ?> +> <div id="alert_messages_block"><?php echo $block->getMessageHtml() ?></div> - -<div class="entry-edit"> -<?php echo $block->getChildHtml('accordion') ?> +<div class="admin__field admin__field-option admin__field-is-downloaodable"> + <input type="checkbox" data-action="change-type-product" class="admin__control-checkbox" + name="is_downloadable" id="is-downloaodable" <?= $block->isDownloadable() ? 'checked="checked"' : ''?> /> + <label class="admin__field-label" for="is-downloaodable"> + <span><?= /* @noEscape */ __('Is this downloadable Product?'); ?></span> + </label> +</div> +<div class="entry-edit" id="product_info_tabs_downloadable_items"> + <?php echo $block->getChildHtml() ?> </div> <div style="display:none"> <div id="custom-advice-container"></div> </div> <?php if ($block->isReadonly()): ?> -<script> -require(['prototype'], function(){ + <script> + require(['prototype'], function(){ -$(<?= /* @escapeNotVerified */ $block->getContentTabId(); ?>).select('input', 'select', 'textarea', 'button').each(function (item){ - item.disabled = true; - if (item.tagName.toLowerCase() == 'button') { - item.addClassName('disabled'); - } -}); + $(<?= /* @escapeNotVerified */ $block->getContentTabId(); ?>).select('input', 'select', 'textarea', 'button').each(function (item){ + item.disabled = true; + if (item.tagName.toLowerCase() == 'button') { + item.addClassName('disabled'); + } + }); -}); -</script> + }); + </script> <?php endif; ?> +</div> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml index 697c74ccb9d1d755ea53609f299443f6bf36c00a..f68fcc7b1963f5406a6bc57d7f33530efd1dfb3d 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml @@ -15,8 +15,9 @@ ?> <?php $_product = $block->getProduct()?> <?php $block->getConfigJson() ?> -<fieldset class="admin__fieldset downloadable-form"> - +<fieldset class="admin__fieldset downloadable-form" data-ui-id="downloadable-links"> + <legend class="admin__legend"><span><?php /* @escapeNotVerified */ echo __('Links')?></span></legend><br> + <p class="note"><?php /* @escapeNotVerified */ echo __('Add links to your product files here.')?></p> <div class="admin__field" <?php echo !$block->isSingleStoreMode() ? ' data-config-scope="' . __('[STORE VIEW]') . '"' : ''; ?>> <label class="admin__field-label" for="downloadable_links_title"><span><?php /* @escapeNotVerified */ echo __('Title')?></span></label> <div class="admin__field-control"> @@ -42,15 +43,15 @@ <table class="admin__control-table"> <thead> <tr> + <th class="col-sort"><span><?php /* @escapeNotVerified */ echo __('Sort Order') ?></span></th> <th class="col-title _required"><span><?php /* @escapeNotVerified */ echo __('Title') ?></span></th> <?php if ($block->getCanReadPrice() !== false) : ?> <th class="col-price"><span><?php /* @escapeNotVerified */ echo __('Price') ?></span></th> <?php endif; ?> - <th class="col-limit"><span><?php /* @escapeNotVerified */ echo __('Max. Downloads') ?></span></th> - <th class="col-option"><span><?php /* @escapeNotVerified */ echo __('Shareable') ?></span></th> + <th class="col-file"><span><?php /* @escapeNotVerified */ echo __('Attach File or Enter Link') ?></span></th> <th class="col-sample"><span><?php /* @escapeNotVerified */ echo __('Sample') ?></span></th> - <th class="col-file"><span><?php /* @escapeNotVerified */ echo __('File') ?></span></th> - <th class="col-sort"><span><?php /* @escapeNotVerified */ echo __('Sort Order') ?></span></th> + <th class="col-share"><span><?php /* @escapeNotVerified */ echo __('Shareable') ?></span></th> + <th class="col-limit"><span><?php /* @escapeNotVerified */ echo __('Max. Downloads') ?></span></th> <th class="col-actions"> </th> </tr> </thead> @@ -80,90 +81,112 @@ require([ ], function(jQuery, registry, mageTemplate){ registry.get('downloadable', function (Downloadable) { var linkTemplate = '<tr>'+ + '<td class="col-sort" data-role="draggable-handle"><input data-container="link-order" type="hidden" ' + + 'name="downloadable[link][<%- ' + + 'data.id' + + ' %>][sort_order]" ' + + 'value="<%- data.sort_order %>" class="input-text admin__control-text sort" />' + + '<span class="draggable-handle" title="<?= /* @escapeNotVerified */ $block->escapeHtml(__('Sort Variations')) ?>"></span>' + + '</td>'+ '<td class="col-title">'+ '<input type="hidden" class="__delete__" name="downloadable[link][<%- data.id %>][is_delete]" value="" />'+ '<input type="hidden" name="downloadable[link][<%- data.id %>][link_id]" value="<%- data.link_id %>" />'+ '<input type="text" class="required-entry input-text admin__control-text" name="downloadable[link][<%- data.id %>][title]" value="<%- data.title %>" />'+ - '<?php echo $_product->getStoreId() ? '<input type="checkbox" id="downloadable_link_<%- data.id %>_title" name="downloadable[link][<%- data.id %>][use_default_title]" value="1" /><label class="normal" for="downloadable_link_<%- data.id %>_title">Use Default Value</label>' : '' ?>'+ + <?php if($_product->getStoreId()): ?> + '<div class="admin__field admin__field-option">'+ + '<input type="checkbox" id="downloadable_link_<%- data.id %>_title" name="downloadable[link][<%- data.id %>][use_default_title]" value="1" class="admin__control-checkbox" />'+ + '<label for="downloadable_link_<%- data.id %>_title" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('Use Default Value'); ?></span></label>'+ + '</div>' + + <?php endif; ?> + <?php if ($block->getCanReadPrice() == false) : ?> + '<input type="hidden" id="downloadable_link_<%- data.id %>_price_value" class="link-prices" name="downloadable[link][<%- data.id %>][price]" value="0" />' + + <?php if ($_product->getStoreId() && $block->getIsPriceWebsiteScope()) : ?> + '<input type="hidden" id="downloadable_link_<%- data.id %>_price" name="downloadable[link][<%- data.id %>][use_default_price]" value="1" />' + + <?php endif; ?> + <?php endif; ?> '</td>'+ <?php if ($block->getCanReadPrice() !== false) : ?> - '<td class="input-price col-price">'+ - '<input type="text" id="downloadable_link_<%- data.id %>_price_value" class="input-text admin__control-text validate-number link-prices<?php if ($block->getCanEditPrice() === false) : ?> disabled<?php endif; ?>" name="downloadable[link][<%- data.id %>][price]" value="<%- data.price %>"<?php if ($block->getCanEditPrice() === false) : ?> disabled="disabled"<?php endif; ?> /> ' + - '<label>[<?php /* @escapeNotVerified */ echo $block->getBaseCurrencyCode($_product->getStoreId()) ?>]</label>' + - <?php if ($_product->getStoreId() && $block->getIsPriceWebsiteScope()) : ?> - '<br /><input type="checkbox" id="downloadable_link_<%- data.id %>_price" name="downloadable[link][<%- data.id %>][use_default_price]" value="1"<?php if ($block->getCanEditPrice() === false) : ?> disabled="disabled"<?php endif; ?> /> <label for="downloadable_link_<%- data.id %>_price">Use Default Value</label>' + - <?php endif; ?> - '</td>' + - <?php else : ?> - '<input type="hidden" id="downloadable_link_<%- data.id %>_price_value" class="link-prices" name="downloadable[link][<%- data.id %>][price]" value="0" />' + - <?php if ($_product->getStoreId() && $block->getIsPriceWebsiteScope()) : ?> - '<input type="hidden" id="downloadable_link_<%- data.id %>_price" name="downloadable[link][<%- data.id %>][use_default_price]" value="1" />' + - <?php endif; ?> + '<td class="col-price">'+ + '<input type="text" id="downloadable_link_<%- data.id %>_price_value" class="input-text admin__control-text validate-number link-prices<?php if ($block->getCanEditPrice() === false) : ?> disabled<?php endif; ?>" name="downloadable[link][<%- data.id %>][price]" value="<%- data.price %>"<?php if ($block->getCanEditPrice() === false) : ?> disabled="disabled"<?php endif; ?> /> ' + + '<div class="note">[<?php /* @escapeNotVerified */ echo $block->getBaseCurrencyCode($_product->getStoreId()) ?>]</div>' + + <?php if ($_product->getStoreId() && $block->getIsPriceWebsiteScope()) : ?> + '<div class="admin__field admin__field-option">'+ + '<input type="checkbox" id="downloadable_link_<%- data.id %>_price" name="downloadable[link][<%- data.id %>][use_default_price]" value="1"<?php if ($block->getCanEditPrice() === false) : ?> disabled="disabled"<?php endif; ?> class="admin__control-checkbox" />'+ + '<label for="downloadable_link_<%- data.id %>_price" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('Use Default Value'); ?></span></label>' + + '</div>' + + <?php endif; ?> + '</td>' + <?php endif; ?> - '<td class="col-limit"><input type="text" id="downloadable_link_<%- data.id %>_downloads" name="downloadable[link][<%- data.id %>][number_of_downloads]" class="input-text admin__control-text downloads" value="<%- data.number_of_downloads %>" />'+ - '<p><input type="checkbox" class="checkbox admin__control-checkbox" id="downloadable_link_<%- data.id %>_is_unlimited" name="downloadable[link][<%- data.id %>][is_unlimited]" value="1" <%- data.is_unlimited %> /> <label for="downloadable_link_<%- data.id %>_is_unlimited">Unlimited</label></p></td>'+ - '<td class="col-share">'+ - '<select id="downloadable_link _<%- data.id %>_shareable" class="admin__control-select" name="downloadable[link][<%- data.id %>][is_shareable]">'+ - '<option value="1">Yes</option>'+ - '<option value="0">No</option>'+ - '<option value="2" selected="selected">Use config</option>'+ - '</select>'+ - '</td>'+ '<td class="col-file">'+ - '<div class="files">'+ - '<div class="row">'+ - '<label for="downloadable_link_<%- data.id %>_sample_file_type"><input type="radio" class="radio" id="downloadable_link_<%- data.id %>_sample_file_type" name="downloadable[link][<%- data.id %>][sample][type]" value="file"<%- data.sample_file_checked %> /> File:</label>'+ - '<input type="hidden" id="downloadable_link_<%- data.id %>_sample_file_save" name="downloadable[link][<%- data.id %>][sample][file]" value="<%- data.sample_file_save %>" class="validate-downloadable-file"/>'+ - '<div id="downloadable_link_<%- data.id %>_sample_file" class="uploader">'+ - '<div id="downloadable_link_<%- data.id %>_sample_file-old" class="file-row-info"></div>'+ - '<div id="downloadable_link_<%- data.id %>_sample_file-new" class="file-row-info"></div>'+ - '<div class="fileinput-button form-buttons">'+ - '<span><?php /* @escapeNotVerified */ echo __('Browse Files...') ?></span>' + - '<input id="downloadable_link_<%- data.id %>_sample_file" type="file" name="<?php /* @escapeNotVerified */ echo $block->getFileFieldName('link_samples') ?>">' + - '<script>' + - 'linksUploader("#downloadable_link_<%- data.id %>_sample_file", "<?php /* @escapeNotVerified */ echo $block->getUploadUrl('link_samples') ?>"); ' + - '</scr'+'ipt>'+ - '</div>'+ - '<div class="clear"></div>'+ + '<div class="admin__field admin__field-option">'+ + '<input type="radio" class="admin__control-radio validate-one-required-by-name" id="downloadable_link_<%- data.id %>_file_type" name="downloadable[link][<%- data.id %>][type]" value="file"<%- data.file_checked %> />' + + '<label for="downloadable_link_<%- data.id %>_file_type" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('File'); ?></span></label>'+ + '<input type="hidden" class="validate-downloadable-file" id="downloadable_link_<%- data.id %>_file_save" name="downloadable[link][<%- data.id %>][file]" value="<%- data.file_save %>" />'+ + + '<div id="downloadable_link_<%- data.id %>_file" class="admin__field-uploader">'+ + '<div id="downloadable_link_<%- data.id %>_file-old" class="file-row-info"></div>'+ + '<div id="downloadable_link_<%- data.id %>_file-new" class="file-row-info new-file"></div>'+ + '<div class="fileinput-button form-buttons">'+ + '<span><?php /* @escapeNotVerified */ echo __('Browse Files...') ?></span>' + + '<input id="downloadable_link_<%- data.id %>_file" type="file" name="<?php /* @escapeNotVerified */ echo $block->getFileFieldName('links') ?>">' + + '<script>' + + 'linksUploader("#downloadable_link_<%- data.id %>_file", "<?php /* @escapeNotVerified */ echo $block->getUploadUrl('links') ?>"); ' + + '</scr'+'ipt>'+ '</div>'+ '</div>'+ - '<div class="row">'+ - '<label for="downloadable_link_<%- data.id %>_sample_url_type"><input type="radio" class="radio" id="downloadable_link_<%- data.id %>_sample_url_type" name="downloadable[link][<%- data.id %>][sample][type]" value="url"<%- data.sample_url_checked %> /> URL:</label><input type="text" class="input-text admin__control-text validate-downloadable-url validate-url" name="downloadable[link][<%- data.id %>][sample][url]" value="<%- data.sample_url %>" />'+ - '</div>'+ - '<div>'+ - '<span id="downloadable_link_<%- data.id %>_sample_container"></span>'+ - '</div>'+ + '</div>'+ + '<div class="admin__field admin__field-option admin__field-file-url">'+ + '<input type="radio" class="admin__control-radio validate-one-required-by-name" id="downloadable_link_<%- data.id %>_url_type" name="downloadable[link][<%- data.id %>][type]" value="url"<%- data.url_checked %> />' + + '<label for="downloadable_link_<%- data.id %>_url_type" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('URL'); ?></span></label>' + + '<input type="text" class="validate-downloadable-url validate-url admin__control-text" name="downloadable[link][<%- data.id %>][link_url]" value="<%- data.link_url %>" placeholder="<?php /* @escapeNotVerified */ echo __('URL'); ?>" />'+ + '</div>'+ + '<div>'+ + '<span id="downloadable_link_<%- data.id %>_link_container"></span>'+ '</div>'+ '</td>'+ - '<td class="col-file">'+ - '<div class="files">'+ - '<div class="row">'+ - '<label for="downloadable_link_<%- data.id %>_file_type"><input type="radio" class="radio validate-one-required-by-name" id="downloadable_link_<%- data.id %>_file_type" name="downloadable[link][<%- data.id %>][type]" value="file"<%- data.file_checked %> /> File:</label>'+ - '<input type="hidden" class="validate-downloadable-file" id="downloadable_link_<%- data.id %>_file_save" name="downloadable[link][<%- data.id %>][file]" value="<%- data.file_save %>" />'+ - '<div id="downloadable_link_<%- data.id %>_file" class="uploader">'+ - '<div id="downloadable_link_<%- data.id %>_file-old" class="file-row-info"></div>'+ - '<div id="downloadable_link_<%- data.id %>_file-new" class="file-row-info new-file"></div>'+ - '<div class="fileinput-button form-buttons">'+ - '<span><?php /* @escapeNotVerified */ echo __('Browse Files...') ?></span>' + - '<input id="downloadable_link_<%- data.id %>_file" type="file" name="<?php /* @escapeNotVerified */ echo $block->getFileFieldName('links') ?>">' + - '<script>' + - 'linksUploader("#downloadable_link_<%- data.id %>_file", "<?php /* @escapeNotVerified */ echo $block->getUploadUrl('links') ?>"); ' + - '</scr'+'ipt>'+ - '</div>'+ - '<div class="clear"></div>'+ + '<td class="col-sample">'+ + '<div class="admin__field admin__field-option">'+ + '<input type="radio" class="admin__control-radio" id="downloadable_link_<%- data.id %>_sample_file_type" name="downloadable[link][<%- data.id %>][sample][type]" value="file"<%- data.sample_file_checked %> />' + + '<label for="downloadable_link_<%- data.id %>_sample_file_type" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('File'); ?>:</span></label>'+ + '<input type="hidden" id="downloadable_link_<%- data.id %>_sample_file_save" name="downloadable[link][<%- data.id %>][sample][file]" value="<%- data.sample_file_save %>" class="validate-downloadable-file"/>'+ + '<div id="downloadable_link_<%- data.id %>_sample_file" class="admin__field-uploader">'+ + '<div id="downloadable_link_<%- data.id %>_sample_file-old" class="file-row-info"></div>'+ + '<div id="downloadable_link_<%- data.id %>_sample_file-new" class="file-row-info new-file"></div>'+ + '<div class="fileinput-button form-buttons">'+ + '<span><?php /* @escapeNotVerified */ echo __('Browse Files...') ?></span>' + + '<input id="downloadable_link_<%- data.id %>_sample_file" type="file" name="<?php /* @escapeNotVerified */ echo $block->getFileFieldName('link_samples') ?>">' + + '<script>'+ + 'linksUploader("#downloadable_link_<%- data.id %>_sample_file", "<?php /* @escapeNotVerified */ echo $block->getUploadUrl('link_samples') ?>"); ' + + '</scr'+'ipt>'+ '</div>'+ '</div>'+ - '<div class="row">'+ - '<label for="downloadable_link_<%- data.id %>_url_type"><input type="radio" class="radio validate-one-required-by-name" id="downloadable_link_<%- data.id %>_url_type" name="downloadable[link][<%- data.id %>][type]" value="url"<%- data.url_checked %> /> URL:</label><input type="text" class="validate-downloadable-url validate-url input-text admin__control-text" name="downloadable[link][<%- data.id %>][link_url]" value="<%- data.link_url %>" />'+ - '</div>'+ - '<div>'+ - '<span id="downloadable_link_<%- data.id %>_link_container"></span>'+ - '</div>'+ + '</div>'+ + + '<div class="admin__field admin__field-option admin__field-file-url">'+ + '<input type="radio" class="admin__control-radio validate-one-required-by-name" id="downloadable_link_<%- data.id %>_sample_url_type" name="downloadable[link][<%- data.id %>][sample][type]" value="url"<%- data.sample_url_checked %> />' + + '<label for="downloadable_link_<%- data.id %>_sample_url_type" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('URL'); ?></span></label>'+ + '<input type="text" class="validate-downloadable-url validate-url admin__control-text" name="downloadable[link][<%- data.id %>][sample][url]" value="<%- data.sample_url %>" placeholder="<?php /* @escapeNotVerified */ echo __('URL'); ?>" />'+ + '</div>'+ + '<div>'+ + '<span id="downloadable_link_<%- data.id %>_sample_container"></span>'+ '</div>'+ '</td>'+ - '<td class="col-sort"><input type="text" name="downloadable[link][<%- data.id %>][sort_order]" value="<%- data.sort_order %>" class="input-text admin__control-text sort" /></td>'+ + '<td class="col-share">'+ + '<select id="downloadable_link _<%- data.id %>_shareable" class="admin__control-select" name="downloadable[link][<%- data.id %>][is_shareable]">'+ + '<option value="1"><?php /* @escapeNotVerified */ echo __('Yes'); ?></option>'+ + '<option value="0"><?php /* @escapeNotVerified */ echo __('No'); ?></option>'+ + '<option value="2" selected="selected"><?php /* @escapeNotVerified */ echo __('Use config'); ?></option>'+ + '</select>'+ + '</td>'+ + '<td class="col-limit">' + + '<input type="text" id="downloadable_link_<%- data.id %>_downloads" name="downloadable[link][<%- data.id %>][number_of_downloads]" class="input-text admin__control-text downloads" value="<%- data.number_of_downloads %>" />'+ + '<div class="admin__field admin__field-option">' + + '<input type="checkbox" class="admin__control-checkbox" id="downloadable_link_<%- data.id %>_is_unlimited" name="downloadable[link][<%- data.id %>][is_unlimited]" value="1" <%- data.is_unlimited %> />' + + '<label for="downloadable_link_<%- data.id %>_is_unlimited" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('Unlimited'); ?></span></label>' + + '</div>' + + '</td>'+ '<td class="col-action">'+ - '<button id="downloadable_link_<%- data.id %>_delete_button" type="button" class="action-delete"><span><?php /* @escapeNotVerified */ echo __('Delete'); ?></span></button>'+ + '<button id="downloadable_link_<%- data.id %>_delete_button" type="button" class="action-delete" title="<?= /* @escapeNotVerified */ $block->escapeHtml(__('Delete')) ?>"><span><?php /* @escapeNotVerified */ echo __('Delete'); ?></span></button>'+ '</td>'+ '</tr>'; @@ -181,6 +204,7 @@ require([ data.link_type = 'file'; data.sample_type = 'none'; data.number_of_downloads = '<?php /* @escapeNotVerified */ echo $block->getConfigMaxDownloads() ?>'; + data.sort_order = this.itemCount + 1; } data.id = this.itemCount; @@ -196,10 +220,8 @@ require([ data.sample_file_checked = ' checked="checked"'; } - Element.insert(this.tbody, { - 'bottom': this.template({ - data: data - }) + var rowElement = Element.insert(this.tbody, { + 'bottom': this.template({data: data}) }); scopeTitle = $('downloadable_link_'+data.id+'_title'); @@ -296,6 +318,20 @@ require([ this.togglePriceFields(); this.bindRemoveButtons(); }, + sorting: function () { + var list = jQuery(this.tbody); + list.sortable({ + axis: 'y', + handle: '[data-role=draggable-handle]', + items: 'tr', + update: function (event, data) { + list.find('[data-container=link-order]').each(function (i, el) { + jQuery(el).val(i + 1); + }); + }, + tolerance: 'pointer' + }); + }, remove : function(event){ var element = $(Event.findElement(event, 'tr')); alertAlreadyDisplayed = false; @@ -332,8 +368,9 @@ require([ elm.disabled = flag; }); } - } + }; + linkItems.sorting(); linkItems.bindRemoveButtons(); linksUploader = function (id, url) { diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml index dc7a5bfebe25b2ddeede695b0ff8b67f12ef2680..7ea23e633c6c9732a5857a16ddd81065a9db399b 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml @@ -15,14 +15,16 @@ $_product = $block->getProduct(); $block->getConfigJson(); ?> -<fieldset class="admin__fieldset downloadable-form"> +<fieldset class="admin__fieldset downloadable-form" data-ui-id="downloadable-samples"> + <legend class="admin__legend"><span><?php /* @escapeNotVerified */ echo __('Samples')?></span></legend><br> + <p class="note"><?php /* @escapeNotVerified */ echo __('Add product preview files here.')?></p> <div class="admin__field"<?php echo !$block->isSingleStoreMode() ? ' data-config-scope="' . __('[STORE VIEW]') . '"' : ''; ?>> - <label class="admin__field-label" for="downloadable_samples_title"><span><?php /* @escapeNotVerified */ echo __('Title')?></span></label> + <label class="admin__field-label" for="downloadable_samples_title"><span><?= /* @noEscape */ __('Title')?></span></label> <div class="admin__field-control"> - <input type="text" class="admin__control-text" id="downloadable_samples_title" name="product[samples_title]" value="<?php /* @escapeNotVerified */ echo $block->getSamplesTitle() ?>" <?php echo($_product->getStoreId() && $block->getUsedDefault()) ? 'disabled="disabled"' : '' ?>> + <input type="text" class="admin__control-text" id="downloadable_samples_title" name="product[samples_title]" value="<?= /* @noEscape */ $block->getSamplesTitle() ?>" <?= /* @noEscape */ ($_product->getStoreId() && $block->getUsedDefault()) ? 'disabled="disabled"' : '' ?>> <?php if ($_product->getStoreId()): ?> <div class="admin__field admin__field-option"> - <input id="sample_title_default" class="admin__control-checkbox" type="checkbox" name="use_default[]" value="samples_title" onclick="toggleValueElements(this, this.parentNode.parentNode)" <?php echo $block->getUsedDefault() ? 'checked="checked"' : '' ?> /> + <input id="sample_title_default" class="admin__control-checkbox" type="checkbox" name="use_default[]" value="samples_title" onclick="toggleValueElements(this, this.parentNode.parentNode)" <?= /* @noEscape */ $block->getUsedDefault() ? 'checked="checked"' : '' ?> /> <label class="admin__field-label" for="sample_title_default"><span>Use Default Value</span></label> </div> <?php endif; ?> @@ -34,15 +36,15 @@ $block->getConfigJson(); <table class="admin__control-table"> <thead> <tr> - <th class="_required col-title"><span><?php /* @escapeNotVerified */ echo __('Title') ?></span></th> - <th class="col-file"><span><?php /* @escapeNotVerified */ echo __('File') ?></span></th> - <th class="col-sort"><span><?php /* @escapeNotVerified */ echo __('Sort Order') ?></span></th> + <th class="col-sort"><span><?= /* @noEscape */ __('Sort Order') ?></span></th> + <th class="_required col-title"><span><?= /* @noEscape */ __('Title') ?></span></th> + <th class="col-file"><span><?= /* @noEscape */ __('Attach File or Enter Link') ?></span></th> <th class="col-actions"> </th> </tr> </thead> <tfoot> <tr> - <td colspan="4" class="col-actions"><?php echo $block->getAddButtonHtml() ?></td> + <td colspan="4" class="col-actions-add"><?php echo $block->getAddButtonHtml() ?></td> </tr> </tfoot> <tbody id="sample_items_body"> @@ -50,7 +52,7 @@ $block->getConfigJson(); </table> </div> <div class="admin__field-note"> - <span><?php /* @escapeNotVerified */ echo __('Alphanumeric, dash and underscore characters are recommended for filenames. Improper characters are replaced with \'_\'.')?></span> + <?= /* @noEscape */ __('Alphanumeric, dash and underscore characters are recommended for filenames. Improper characters are replaced with \'_\'.')?> </div> </div> </div> @@ -65,46 +67,53 @@ require([ ], function (jQuery, registry, mageTemplate) { registry.get('downloadable', function (Downloadable) { var sampleTemplate = '<tr>'+ - '<td class="col-title">'+ - '<input type="hidden" class="__delete__" name="downloadable[sample][<%- data.id %>][is_delete]" value="" />'+ - '<input type="hidden" name="downloadable[sample][<%- data.id %>][sample_id]" value="<%- data.sample_id %>" />'+ - '<input type="text" class="required-entry input-text admin__control-text" name="downloadable[sample][<%- data.id %>][title]" value="<%- data.title %>" />'+ - '<?php echo $_product->getStoreId() ? '<br /><input type="checkbox" id="downloadable_sample_<%- data.id %>_title" name="downloadable[sample][<%- data.id %>][use_default_title]" value="1" /><label class="normal" for="downloadable_sample_<%- data.id %>_title">Use Default Value</label>' : '' ?>'+ - '</td>'+ - '<td class="col-file">'+ - '<div class="files-wide">'+ - '<div class="row">'+ - '<label for="downloadable_sample_<%- data.id %>_file_type"><input type="radio" class="radio validate-one-required-by-name" id="downloadable_sample_<%- data.id %>_file_type" name="downloadable[sample][<%- data.id %>][type]" value="file"<%- data.file_checked %> /> File:</label>'+ - '<input type="hidden" class="validate-downloadable-file" id="downloadable_sample_<%- data.id %>_file_save" name="downloadable[sample][<%- data.id %>][file]" value="<%- data.file_save %>" />'+ - '<div id="downloadable_sample_<%- data.id %>_file" class="uploader">'+ - '<div id="downloadable_sample_<%- data.id %>_file-old" class="file-row-info"></div>'+ - '<div id="downloadable_sample_<%- data.id %>_file-new" class="file-row-info new-file"></div>'+ - '<div class="fileinput-button form-buttons">'+ - '<span><?php /* @escapeNotVerified */ echo __('Browse Files...') ?></span>' + - '<input id="downloadable_sample_<%- data.id %>_file" type="file" name="<?php /* @escapeNotVerified */ echo $block->getConfig()->getFileField() ?>" data-url="<?php /* @escapeNotVerified */ echo $block->getConfig()->getUrl() ?>">' + - '<script>' + - '/*<![CDATA[*/' + - 'sampleUploader("#downloadable_sample_<%- data.id %>_file"); ' + - '/*]]>*/' + - '</scr'+'ipt>'+ - '</div>'+ - '<div class="clear"></div>'+ - '</div>'+ - '</div>'+ - '<div class="row">'+ - '<label for="downloadable_sample_<%- data.id %>_url_type"><input type="radio" class="radio validate-one-required-by-name" id="downloadable_sample_<%- data.id %>_url_type" name="downloadable[sample][<%- data.id %>][type]" value="url"<%- data.url_checked %> /> URL:</label> <input type="text" class="validate-downloadable-url validate-url input-text admin__control-text" name="downloadable[sample][<%- data.id %>][sample_url]" value="<%- data.sample_url %>" />'+ - '</div>'+ - '<div>'+ - '<span id="downloadable_sample_<%- data.id %>_container"></span>'+ - '</div>'+ - '</div>'+ - - '</td>'+ - '<td class="col-sort"><input type="text" name="downloadable[sample][<%- data.id %>][sort_order]" value="<%- data.sort_order %>" class="input-text sort admin__control-text" /></td>'+ - '<td class="col-actions">'+ - '<button type="button" class="action-delete"><span>Delete</span></button>'+ - '</td>'+ - '</tr>'; + '<td class="col-sort" data-role="draggable-handle">' + + '<input data-container="link-order" type="hidden" name="downloadable[sample][<%- data.id %>][sort_order]" value="<%- data.sort_order %>" class="sort" />' + + '<span class="draggable-handle" title="<?= /* @escapeNotVerified */ $block->escapeHtml(__('Sort Variations')) ?>"></span>' + + '</td>'+ + '<td class="col-title">'+ + '<input type="hidden" class="__delete__" name="downloadable[sample][<%- data.id %>][is_delete]" value="" />'+ + '<input type="hidden" name="downloadable[sample][<%- data.id %>][sample_id]" value="<%- data.sample_id %>" />'+ + '<input type="text" class="required-entry input-text admin__control-text" name="downloadable[sample][<%- data.id %>][title]" value="<%- data.title %>" />'+ + <?php if($_product->getStoreId()): ?> + '<div class="admin__field admin__field-option">'+ + '<input type="checkbox" id="downloadable_sample_<%- data.id %>_title" name="downloadable[sample][<%- data.id %>][use_default_title]" value="1" class="admin__control-checkbox" />'+ + '<label for="downloadable_link_<%- data.id %>_price" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('Use Default Value'); ?></span></label>'+ + '</div>' + + <?php endif; ?> + '</td>'+ + '<td class="col-file">'+ + '<div class="admin__field admin__field-option">'+ + '<input type="radio" class="admin__control-radio validate-one-required-by-name" id="downloadable_sample_<%- data.id %>_file_type" name="downloadable[sample][<%- data.id %>][type]" value="file"<%- data.file_checked %> />' + + '<label for="downloadable_sample_<%- data.id %>_file_type" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('File'); ?>:</span></label>'+ + '<input type="hidden" class="validate-downloadable-file" id="downloadable_sample_<%- data.id %>_file_save" name="downloadable[sample][<%- data.id %>][file]" value="<%- data.file_save %>" />'+ + '<div id="downloadable_sample_<%- data.id %>_file" class="admin__field-uploader">'+ + '<div id="downloadable_sample_<%- data.id %>_file-old" class="file-row-info"></div>'+ + '<div id="downloadable_sample_<%- data.id %>_file-new" class="file-row-info new-file"></div>'+ + '<div class="fileinput-button">'+ + '<span><?= /* @noEscape */ __('Browse Files...') ?></span>' + + '<input id="downloadable_sample_<%- data.id %>_file" type="file" name="<?= /* @noEscape */ $block->getConfig()->getFileField() ?>" data-url="<?= /* @noEscape */ $block->getConfig()->getUrl() ?>">' + + '<script>' + + '/*<![CDATA[*/' + + 'sampleUploader("#downloadable_sample_<%- data.id %>_file"); ' + + '/*]]>*/' + + '</scr'+'ipt>'+ + '</div>'+ + '</div>'+ + '</div>'+ + '<div class="admin__field admin__field-option admin__field-file-url">'+ + '<input type="radio" class="admin__control-radio validate-one-required-by-name" id="downloadable_sample_<%- data.id %>_url_type" name="downloadable[sample][<%- data.id %>][type]" value="url"<%- data.url_checked %> />' + + '<label for="downloadable_sample_<%- data.id %>_url_type" class="admin__field-label"><span><?php /* @escapeNotVerified */ echo __('URL'); ?></span></label>' + + '<input type="text" class="validate-downloadable-url validate-url admin__control-text" name="downloadable[sample][<%- data.id %>][sample_url]" value="<%- data.sample_url %>" placeholder="<?php /* @escapeNotVerified */ echo __('URL'); ?>" />'+ + '</div>'+ + '<div>'+ + '<span id="downloadable_sample_<%- data.id %>_container"></span>'+ + '</div>'+ + '</td>'+ + '<td class="col-actions">'+ + '<button type="button" class="action-delete" title="<?= /* @escapeNotVerified */ $block->escapeHtml(__('Delete')); ?>"><span><?php /* @escapeNotVerified */ echo __('Delete'); ?></span></button>'+ + '</td>'+ + '</tr>'; sampleItems = { tbody: $('sample_items_body'), templateText: sampleTemplate, @@ -117,6 +126,7 @@ require([ data = {}; data.sample_type = 'file'; data.sample_id = 0; + data.sort_order = this.itemCount + 1; } data.id = this.itemCount; @@ -158,11 +168,11 @@ require([ new Downloadable.FileUploader( 'samples', data.id, - sampleUrl.up('td').down('div.uploader'), + sampleUrl.up('td').down('div.admin__field-uploader'), 'downloadable[sample]['+data.id+']', data.file_save, 'downloadable_sample_'+data.id+'_file', - <?php /* @escapeNotVerified */ echo $block->getConfigJson() ?> + <?= /* @noEscape */ $block->getConfigJson() ?> ); sampleUrl.advaiceContainer = 'downloadable_sample_'+data.id+'_container'; sampleFile = $('downloadable_sample_'+data.id+'_file_type'); @@ -172,6 +182,20 @@ require([ this.itemCount++; this.bindRemoveButtons(); }, + sorting: function () { + var list = jQuery(this.tbody); + list.sortable({ + axis: 'y', + handle: '[data-role=draggable-handle]', + items: 'tr', + update: function (event, data) { + list.find('[data-container=link-order]').each(function (i, el) { + jQuery(el).val(i + 1); + }); + }, + tolerance: 'pointer' + }); + }, remove: function(event) { var element = $(Event.findElement(event, 'tr')); alertAlreadyDisplayed = false; @@ -191,15 +215,16 @@ require([ } } } - } + }; + sampleItems.sorting(); sampleItems.bindRemoveButtons(); sampleUploader = function (id) { (function ($, id) { $(id).fileupload({ dataType: 'json', - url: '<?php /* @escapeNotVerified */ echo $block->getConfig()->getUrl() ?>', + url: '<?= /* @noEscape */ $block->getConfig()->getUrl() ?>', sequentialUploads: true, maxFileSize: 2000000, add: function (e, data) { @@ -263,7 +288,7 @@ require([ } <?php foreach ($block->getSampleData() as $item): ?> - sampleItems.add(<?php /* @escapeNotVerified */ echo $item->toJson() ?>); + sampleItems.add(<?= /* @noEscape */ $item->toJson() ?>); <?php endforeach; ?> }); diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php index 4be3ddab952c3836caf836d94cbcdf0ab832d3a8..740c95ba6fb74e2e732f5c317685fdbe6dbcc5f3 100644 --- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php +++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php @@ -335,6 +335,7 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn * @throws \Magento\Framework\Exception\LocalizedException * * @see self::_getConditionSql for $condition + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner') { @@ -344,7 +345,13 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn } if (is_numeric($attribute)) { - $attribute = $this->getEntity()->getAttribute($attribute)->getAttributeCode(); + $attributeModel = $this->getEntity()->getAttribute($attribute); + if (!$attributeModel) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Invalid attribute identifier for filter (%1)', get_class($attribute)) + ); + } + $attribute = $attributeModel->getAttributeCode(); } elseif ($attribute instanceof \Magento\Eav\Model\Entity\Attribute\AttributeInterface) { $attribute = $attribute->getAttributeCode(); } diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php index 883ca66f6a822947c9f928f857791dcca9b8e590..011f79252967ac3101251f832a76c278dc7fa620 100644 --- a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php +++ b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php @@ -163,6 +163,9 @@ class Data extends \Magento\Framework\Validator\AbstractValidator $attributesByCode = []; $attributesCodes = []; foreach ($attributes as $attribute) { + if (!$attribute->getIsVisible()) { + continue; + } $attributeCode = $attribute->getAttributeCode(); $attributesByCode[$attributeCode] = $attribute; $attributesCodes[] = $attributeCode; diff --git a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php index e8c80df9efbf701c6bb6afcc6b9a6e33ce3ec7f6..d39ee81d5ae452670239fe6856fcee8907eaf817 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php @@ -44,16 +44,10 @@ class DataTest extends \PHPUnit_Framework_TestCase $validator->setAttributes([$attribute])->setData($data); if ($attribute->getDataModel() || $attribute->getFrontendInput()) { $dataModel = $this->_getDataModelMock($result); - $attrDataFactory->expects( - $this->once() - )->method( - 'create' - )->with( - $attribute, - $entity - )->will( - $this->returnValue($dataModel) - ); + $attrDataFactory->expects($this->any()) + ->method('create') + ->with($attribute, $entity) + ->will($this->returnValue($dataModel)); } $this->assertEquals($expected, $validator->isValid($entity)); $this->assertEquals($messages, $validator->getMessages()); @@ -72,6 +66,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ], 'attributeReturns' => true, 'isValid' => true, @@ -82,19 +77,27 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ], 'attributeReturns' => ['Error'], 'isValid' => false, 'messages' => ['attribute' => ['Error']], ], 'no_data_models' => [ - 'attributeData' => ['attribute_code' => 'attribute', 'frontend_input' => 'text'], + 'attributeData' => [ + 'attribute_code' => 'attribute', + 'frontend_input' => 'text', + 'is_visible' => true, + ], 'attributeReturns' => ['Error'], 'isValid' => false, 'messages' => ['attribute' => ['Error']], ], 'no_data_models_no_frontend_input' => [ - 'attributeData' => ['attribute_code' => 'attribute'], + 'attributeData' => [ + 'attribute_code' => 'attribute', + 'is_visible' => true, + ], 'attributeReturns' => ['Error'], 'isValid' => true, 'messages' => [], @@ -104,6 +107,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ], 'attributeReturns' => true, 'isValid' => true, @@ -115,12 +119,24 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ], 'attributeReturns' => true, 'isValid' => true, 'messages' => [], 'setData' => [], - ] + ], + 'is_invisible' => [ + 'attributeData' => [ + 'attribute_code' => 'attribute', + 'data_model' => $this->_getDataModelMock(null), + 'frontend_input' => 'text', + 'is_visible' => false, + ], + 'attributeReturns' => ['Error'], + 'isValid' => true, + 'messages' => [], + ], ]; } @@ -138,6 +154,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ] ); $collection = $this->getMockBuilder('Magento\Framework\DataObject')->setMethods(['getItems'])->getMock(); @@ -185,6 +202,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ] ); $secondAttribute = $this->_getAttributeMock( @@ -192,6 +210,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute2', 'data_model' => $this->_getDataModelMock(null), 'frontend_input' => 'text', + 'is_visible' => true, ] ); $data = ['attribute' => 'new_test_data', 'attribute2' => 'some data']; @@ -279,6 +298,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute1', 'data_model' => $firstDataModel = $this->_getDataModelMock(['Error1']), 'frontend_input' => 'text', + 'is_visible' => true, ] ); $secondAttribute = $this->_getAttributeMock( @@ -286,6 +306,7 @@ class DataTest extends \PHPUnit_Framework_TestCase 'attribute_code' => 'attribute2', 'data_model' => $secondDataModel = $this->_getDataModelMock(['Error2']), 'frontend_input' => 'text', + 'is_visible' => true, ] ); $expectedMessages = ['attribute1' => ['Error1'], 'attribute2' => ['Error2']]; @@ -354,11 +375,17 @@ class DataTest extends \PHPUnit_Framework_TestCase */ protected function _getAttributeMock($attributeData) { - $attribute = $this->getMockBuilder( - 'Magento\Eav\Model\Attribute' - )->setMethods( - ['getAttributeCode', 'getDataModel', 'getFrontendInput', '__wakeup'] - )->disableOriginalConstructor()->getMock(); + $attribute = $this->getMockBuilder('Magento\Eav\Model\Attribute') + ->setMethods([ + 'getAttributeCode', + 'getDataModel', + 'getFrontendInput', + '__wakeup', + 'getIsVisible', + ]) + ->disableOriginalConstructor() + ->getMock(); + if (isset($attributeData['attribute_code'])) { $attribute->expects( $this->any() @@ -386,6 +413,11 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->returnValue($attributeData['frontend_input']) ); } + if (isset($attributeData['is_visible'])) { + $attribute->expects($this->any()) + ->method('getIsVisible') + ->willReturn($attributeData['is_visible']); + } return $attribute; } diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php index 8c02fcacecb6ede4375e56445974ec11c15740eb..b2e8e0b98542f017ba5c8cdf9976bfc022a38744 100644 --- a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php @@ -4,11 +4,12 @@ * See COPYING.txt for license details. */ + +namespace Magento\EncryptionKey\Controller\Adminhtml\Crypt; + /** * Encryption key changer controller */ -namespace Magento\EncryptionKey\Controller\Adminhtml\Crypt; - abstract class Key extends \Magento\Backend\App\Action { /** diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php index b294d98df9c1c69cf2b1b44d85dfef2e72721058..926d00e4f673be970dcf1dcbf4a556e2e8a93538 100644 --- a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php @@ -64,10 +64,10 @@ class Save extends \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key } $newKey = $this->change->changeEncryptionKey($key); - $this->messageManager->addSuccess(__('The encryption key has been changed.')); + $this->messageManager->addSuccessMessage(__('The encryption key has been changed.')); if (!$key) { - $this->messageManager->addNotice( + $this->messageManager->addNoticeMessage( __( 'This is your new encryption key: <span style="font-family:monospace;">%1</span>. ' . 'Be sure to write it down and take good care of it!', @@ -77,7 +77,7 @@ class Save extends \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key } $this->cache->clean(); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_session->setFormData(['crypt_key' => $key]); } $this->_redirect('adminhtml/*/'); diff --git a/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php b/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php index 2ebb4e2cc3f8eb7f2073ece28f43801e7090e52e..a211eb523b410dc017092b9cbcbae27f3d862341 100644 --- a/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php +++ b/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php @@ -77,35 +77,6 @@ class Change extends \Magento\Framework\Model\Resource\Db\AbstractDb $this->_init('core_config_data', 'config_id'); } - /** - * Re-encrypt all encrypted data in the database - * - * TODO: seems not used - * - * @param bool $safe Specifies whether wrapping re-encryption into the database transaction or not - * @return void - * @throws \Exception - */ - public function reEncryptDatabaseValues($safe = true) - { - // update database only - if ($safe) { - $this->beginTransaction(); - } - try { - $this->_reEncryptSystemConfigurationValues(); - $this->_reEncryptCreditCardNumbers(); - if ($safe) { - $this->commit(); - } - } catch (\Exception $e) { - if ($safe) { - $this->rollBack(); - } - throw $e; - } - } - /** * Change encryption key * diff --git a/app/code/Magento/EncryptionKey/Test/Unit/Controller/Adminhtml/Crypt/Key/SaveTest.php b/app/code/Magento/EncryptionKey/Test/Unit/Controller/Adminhtml/Crypt/Key/SaveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ef30546eb3ba7efd1cb7a08b49f328ce162a1a14 --- /dev/null +++ b/app/code/Magento/EncryptionKey/Test/Unit/Controller/Adminhtml/Crypt/Key/SaveTest.php @@ -0,0 +1,130 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\EncryptionKey\Test\Unit\Controller\Adminhtml\Crypt\Key; + +/** + * Test class for Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key\Save + */ +class SaveTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $encryptMock; + /** @var \Magento\EncryptionKey\Model\Resource\Key\Change|\PHPUnit_Framework_MockObject_MockObject */ + protected $changeMock; + /** @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $cacheMock; + /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $requestMock; + /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $managerMock; + /** @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $responseMock; + /** @var \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key\Save */ + protected $model; + + public function setUp() + { + $this->encryptMock = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->changeMock = $this->getMockBuilder('Magento\EncryptionKey\Model\Resource\Key\Change') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->cacheMock = $this->getMockBuilder('Magento\Framework\App\CacheInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->requestMock = $this->getMockBuilder('Magento\Framework\App\RequestInterface') + ->disableOriginalConstructor() + ->setMethods(['getPost']) + ->getMockForAbstractClass(); + $this->managerMock = $this->getMockBuilder('\Magento\Framework\Message\ManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->responseMock = $this->getMockBuilder('Magento\Framework\App\ResponseInterface') + ->disableOriginalConstructor() + ->setMethods(['setRedirect']) + ->getMockForAbstractClass(); + + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->model = $helper->getObject( + 'Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key\Save', + [ + 'encryptor' => $this->encryptMock, + 'change' => $this->changeMock, + 'cache' => $this->cacheMock, + 'request' => $this->requestMock, + 'messageManager' => $this->managerMock, + 'response' => $this->responseMock, + ] + ); + } + + public function testExecuteNonRandomAndWithCryptKey() + { + $expectedMessage = 'The encryption key has been changed.'; + $key = 1; + $newKey = 'RSASHA9000VERYSECURESUPERMANKEY'; + $this->requestMock + ->expects($this->at(0)) + ->method('getPost') + ->with($this->equalTo('generate_random')) + ->willReturn(0); + $this->requestMock + ->expects($this->at(1)) + ->method('getPost') + ->with($this->equalTo('crypt_key')) + ->willReturn($key); + $this->encryptMock->expects($this->once())->method('validateKey'); + $this->changeMock->expects($this->once())->method('changeEncryptionKey')->willReturn($newKey); + $this->managerMock->expects($this->once())->method('addSuccessMessage')->with($expectedMessage); + $this->cacheMock->expects($this->once())->method('clean'); + $this->responseMock->expects($this->once())->method('setRedirect'); + + $this->model->execute(); + } + + public function testExecuteNonRandomAndWithoutCryptKey() + { + $key = null; + $this->requestMock + ->expects($this->at(0)) + ->method('getPost') + ->with($this->equalTo('generate_random')) + ->willReturn(0); + $this->requestMock + ->expects($this->at(1)) + ->method('getPost') + ->with($this->equalTo('crypt_key')) + ->willReturn($key); + $this->managerMock->expects($this->once())->method('addErrorMessage'); + + $this->model->execute(); + } + + public function testExecuteRandom() + { + $newKey = 'RSASHA9000VERYSECURESUPERMANKEY'; + $this->requestMock + ->expects($this->at(0)) + ->method('getPost') + ->with($this->equalTo('generate_random')) + ->willReturn(1); + $this->changeMock->expects($this->once())->method('changeEncryptionKey')->willReturn($newKey); + $this->managerMock->expects($this->once())->method('addSuccessMessage'); + $this->managerMock->expects($this->once())->method('addNoticeMessage'); + $this->cacheMock->expects($this->once())->method('clean'); + $this->responseMock->expects($this->once())->method('setRedirect'); + + $this->model->execute(); + } +} diff --git a/app/code/Magento/EncryptionKey/Test/Unit/Model/Resource/Key/ChangeTest.php b/app/code/Magento/EncryptionKey/Test/Unit/Model/Resource/Key/ChangeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c0793ee43c544e3419c3e6c594befb4afe89272e --- /dev/null +++ b/app/code/Magento/EncryptionKey/Test/Unit/Model/Resource/Key/ChangeTest.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\EncryptionKey\Test\Unit\Model\Resource\Key; + +/** + * Test Class For Magento\EncryptionKey\Model\Resource\Key\Change + */ +class ChangeTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $encryptMock; + /** @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject */ + protected $filesystemMock; + /** @var \Magento\Config\Model\Config\Structure|\PHPUnit_Framework_MockObject_MockObject */ + protected $structureMock; + /** @var \Magento\Framework\App\DeploymentConfig\Writer|\PHPUnit_Framework_MockObject_MockObject */ + protected $writerMock; + /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $adapterMock; + /** @var \Magento\Framework\App\Resource|\PHPUnit_Framework_MockObject_MockObject */ + protected $resourceMock; + /** @var \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject */ + protected $selectMock; + /** @var \Magento\Framework\Model\Resource\Db\TransactionManagerInterface */ + protected $tansactionMock; + /** @var \Magento\Framework\Model\Resource\Db\ObjectRelationProcessor|\PHPUnit_Framework_MockObject_MockObject */ + protected $objRelationMock; + /** @var \Magento\EncryptionKey\Model\Resource\Key\Change */ + protected $model; + + public function setUp() + { + $this->encryptMock = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface') + ->disableOriginalConstructor() + ->setMethods(['setNewKey', 'exportKeys']) + ->getMockForAbstractClass(); + $this->filesystemMock = $this->getMockBuilder('Magento\Framework\Filesystem') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->structureMock = $this->getMockBuilder('Magento\Config\Model\Config\Structure') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->writerMock = $this->getMockBuilder('Magento\Framework\App\DeploymentConfig\Writer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->adapterMock = $this->getMockBuilder('Magento\Framework\DB\Adapter\AdapterInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->resourceMock = $this->getMockBuilder('Magento\Framework\App\Resource') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->selectMock = $this->getMockBuilder('Magento\Framework\DB\Select') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->tansactionMock = $this->getMockBuilder('Magento\Framework\Model\Resource\Db\TransactionManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->objRelationMock = $this->getMockBuilder('Magento\Framework\Model\Resource\Db\ObjectRelationProcessor') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->model = $helper->getObject( + 'Magento\EncryptionKey\Model\Resource\Key\Change', + [ + 'filesystem' => $this->filesystemMock, + 'structure' => $this->structureMock, + 'encryptor' => $this->encryptMock, + 'writer' => $this->writerMock, + 'adapterInterface' => $this->adapterMock, + 'resource' => $this->resourceMock, + 'transactionManager' => $this->tansactionMock, + 'relationProcessor' => $this->objRelationMock + ] + ); + } + + public function testChangeEncryptionKey() + { + $paths = ['path1', 'path2']; + $table = ['item1', 'item2']; + $values = [ + 'key1' => 'value1', + 'key2' => 'value2' + ]; + $key = 'key'; + + $this->writerMock->expects($this->once())->method('checkIfWritable')->willReturn(true); + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->adapterMock); + $this->adapterMock->expects($this->once())->method('beginTransaction'); + $this->structureMock->expects($this->once())->method('getFieldPathsByAttribute')->willReturn($paths); + $this->resourceMock->expects($this->atLeastOnce())->method('getTableName')->willReturn($table); + $this->adapterMock->expects($this->any())->method('select')->willReturn($this->selectMock); + $this->adapterMock->expects($this->any())->method('fetchPairs')->willReturn($values); + $this->selectMock->expects($this->any())->method('from')->willReturnSelf(); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturnSelf(); + $this->selectMock->expects($this->any())->method('update')->willReturnSelf(); + $this->writerMock->expects($this->once())->method('saveConfig'); + $this->adapterMock->expects($this->once())->method('getTransactionLevel')->willReturn(1); + + $this->assertEquals($key, $this->model->changeEncryptionKey($key)); + } + + public function testChangeEncryptionKeyThrowsException() + { + $key = 'key'; + $this->writerMock->expects($this->once())->method('checkIfWritable')->willReturn(false); + + try { + $this->model->changeEncryptionKey($key); + } catch (\Exception $e) { + return; + } + + $this->fail('An excpected exception was not signaled.'); + } +} diff --git a/app/code/Magento/GiftMessage/composer.json b/app/code/Magento/GiftMessage/composer.json index c3662869435b747e9c1ebbfc828ed2e2316f1dc9..6230e36ff13e4051816047af7c4960e4b94dc0e5 100644 --- a/app/code/Magento/GiftMessage/composer.json +++ b/app/code/Magento/GiftMessage/composer.json @@ -6,7 +6,6 @@ "magento/module-store": "1.0.0-beta", "magento/module-catalog": "1.0.0-beta", "magento/module-checkout": "1.0.0-beta", - "magento/module-multishipping": "1.0.0-beta", "magento/module-sales": "1.0.0-beta", "magento/module-backend": "1.0.0-beta", "magento/module-customer": "1.0.0-beta", @@ -15,6 +14,9 @@ "magento/framework": "1.0.0-beta", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-multishipping": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/GroupedProduct/Block/Product/Grouped/AssociatedProducts.php b/app/code/Magento/GroupedProduct/Block/Product/Grouped/AssociatedProducts.php index 2f1dbf803cce894c7e7e8e35e3d0b9309952c8f3..2c10789d60cad5542daa5fbaedb15534ab072a28 100644 --- a/app/code/Magento/GroupedProduct/Block/Product/Grouped/AssociatedProducts.php +++ b/app/code/Magento/GroupedProduct/Block/Product/Grouped/AssociatedProducts.php @@ -35,4 +35,23 @@ class AssociatedProducts extends \Magento\Backend\Block\Catalog\Product\Tab\Cont { return 'product-details'; } + + /** + * Tab is hidden + * + * @return boolean + */ + public function isHidden() + { + return false; + } + + /** + * @return $this + */ + protected function _prepareLayout() + { + $this->setData('opened', true); + return $this; + } } diff --git a/app/code/Magento/GroupedProduct/Model/Resource/Product/Indexer/Price/Grouped.php b/app/code/Magento/GroupedProduct/Model/Resource/Product/Indexer/Price/Grouped.php index 545522b98ab7bf5c428375e7bb75d14a671138f2..7605bda8a0ae9b1522a781432d14bfb78a384a5a 100644 --- a/app/code/Magento/GroupedProduct/Model/Resource/Product/Indexer/Price/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/Resource/Product/Indexer/Price/Grouped.php @@ -96,7 +96,6 @@ class Grouped extends \Magento\Catalog\Model\Resource\Product\Indexer\Price\Defa 'min_price' => new \Zend_Db_Expr('MIN(' . $minCheckSql . ')'), 'max_price' => new \Zend_Db_Expr('MAX(' . $maxCheckSql . ')'), 'tier_price' => new \Zend_Db_Expr('NULL'), - 'group_price' => new \Zend_Db_Expr('NULL') ] )->group( ['e.entity_id', 'cg.customer_group_id', 'cw.website_id'] diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php index 3e9523d19787a4abd1fc652b1ed563e420603123..e8f0e4f22914f5d9692e4b88e59a13d2a69d0a80 100644 --- a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php +++ b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php @@ -64,7 +64,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase $expectedFinalPrice ) { $rawFinalPrice = 10; - $rawPriceCheckStep = 10; + $rawPriceCheckStep = 6; $this->productMock->expects( $this->any() @@ -167,7 +167,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase 'custom_option_null' => [ 'associatedProducts' => [], 'options' => [[], []], - 'expectedPriceCall' => 14, /* product call number to check final price formed correctly */ + 'expectedPriceCall' => 6, /* product call number to check final price formed correctly */ 'expectedFinalPrice' => 10, /* 10(product price) + 2(options count) * 5(qty) * 5(option price) */ ], 'custom_option_exist' => [ @@ -177,7 +177,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase ['associated_product_2', $optionMock], ['associated_product_3', $optionMock], ], - 'expectedPriceCall' => 17, /* product call number to check final price formed correctly */ + 'expectedPriceCall' => 16, /* product call number to check final price formed correctly */ 'expectedFinalPrice' => 35, /* 10(product price) + 2(options count) * 5(qty) * 5(option price) */ ] ]; diff --git a/app/code/Magento/GroupedProduct/view/adminhtml/layout/catalog_product_grouped.xml b/app/code/Magento/GroupedProduct/view/adminhtml/layout/catalog_product_grouped.xml index df1ff2634e284bafb8520391ebdd9f74d84bb8aa..220f40e999966287e94f0eda234770458cbfeb22 100644 --- a/app/code/Magento/GroupedProduct/view/adminhtml/layout/catalog_product_grouped.xml +++ b/app/code/Magento/GroupedProduct/view/adminhtml/layout/catalog_product_grouped.xml @@ -12,15 +12,13 @@ <update handle="groupedproduct_popup_grid"/> <body> <referenceBlock name="product_tabs"> - <block class="Magento\GroupedProduct\Block\Product\Grouped\AssociatedProducts" name="catalog.product.edit.grouped.container" template="product/grouped/container.phtml"> - <block class="Magento\Framework\View\Element\Template" name="catalog.product.edit.tab.super.container" template="Magento_GroupedProduct::product/grouped/grouped.phtml"> - <block class="Magento\GroupedProduct\Block\Product\Grouped\AssociatedProducts\ListAssociatedProducts" name="catalog.product.edit.tab.super.list" as="list" template="product/grouped/list.phtml"/> - <block class="Magento\Framework\View\Element\Template" name="catalog.product.edit.tab.super.grid.popup.container" as="catalog.product.group.grid.popup.container"/> - </block> + <block class="Magento\GroupedProduct\Block\Product\Grouped\AssociatedProducts" name="catalog.product.edit.tab.grouped.container" template="Magento_GroupedProduct::product/grouped/grouped.phtml"> + <block class="Magento\GroupedProduct\Block\Product\Grouped\AssociatedProducts\ListAssociatedProducts" name="catalog.product.edit.tab.super.list" as="list" template="product/grouped/list.phtml"/> + <block class="Magento\Framework\View\Element\Template" name="catalog.product.edit.tab.super.grid.popup.container" as="catalog.product.group.grid.popup.container"/> </block> <action method="addTab"> <argument name="name" xsi:type="string">grouped</argument> - <argument name="block" xsi:type="string">catalog.product.edit.grouped.container</argument> + <argument name="block" xsi:type="string">catalog.product.edit.tab.grouped.container</argument> </action> <referenceBlock name="product_tabs.customer_options"> <arguments> diff --git a/app/code/Magento/GroupedProduct/view/adminhtml/templates/product/grouped/container.phtml b/app/code/Magento/GroupedProduct/view/adminhtml/templates/product/grouped/container.phtml deleted file mode 100644 index 9bd1f8212c1f3e27e866ca2644da63aa9908bded..0000000000000000000000000000000000000000 --- a/app/code/Magento/GroupedProduct/view/adminhtml/templates/product/grouped/container.phtml +++ /dev/null @@ -1,27 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - ?> - -<?php /* @var $block \Magento\GroupedProduct\Block\Product\Grouped\AssociatedProducts */ ?> -<div class="entry-edit" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>"> - <div class="fieldset-wrapper admin__collapsible-block-wrapper" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>-wrapper"> - <div class="fieldset-wrapper-title"> - <strong - class="admin__collapsible-title" - data-toggle="collapse" - data-target="#<?php /* @escapeNotVerified */ echo $block->getId() ?>-content"> - <span><?php /* @escapeNotVerified */ echo $block->getTabLabel() ?></span> - </strong> - </div> - <div class="fieldset-wrapper-content in collapse" id="<?php /* @escapeNotVerified */ echo $block->getId() ?>-content"> - <fieldset class="fieldset"> - <?php echo $block->getChildHtml()?> - </fieldset> - </div> - </div> -</div> diff --git a/app/code/Magento/ImportExport/Files/Sample/advanced_pricing.csv b/app/code/Magento/ImportExport/Files/Sample/advanced_pricing.csv index bfd24ea9bcc7eadce4507e02f2953a5351a83ca1..42cdd7017a39599ecd23ab05dd427868847481b0 100644 --- a/app/code/Magento/ImportExport/Files/Sample/advanced_pricing.csv +++ b/app/code/Magento/ImportExport/Files/Sample/advanced_pricing.csv @@ -1,5 +1,5 @@ -sku,tier_price_website,tier_price_customer_group,tier_price_qty,tier_price,group_price_website,group_price_customer_group,group_price_price -sku123,website1,General,2,10,,, -sku124,All Websites [USD],ALL GROUPS,3,15,,, -sku123,,,,,website1,General,11 -sku124,,,,,All Websites [USD],General,12 +sku,tier_price_website,tier_price_customer_group,tier_price_qty,tier_price +sku123,website1,General,2,10 +sku124,All Websites [USD],ALL GROUPS,3,15 +sku123,,,, +sku124,,,, diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json index 14446581255e6b3fd52a7c5dce2fe32077a34a63..b5d603e700e321f17c6233a065f913d76782285c 100644 --- a/app/code/Magento/Msrp/composer.json +++ b/app/code/Magento/Msrp/composer.json @@ -4,7 +4,6 @@ "require": { "php": "~5.5.0|~5.6.0|~7.0.0", "magento/module-store": "1.0.0-beta", - "magento/module-bundle": "1.0.0-beta", "magento/module-catalog": "1.0.0-beta", "magento/module-downloadable": "1.0.0-beta", "magento/module-eav": "1.0.0-beta", @@ -14,6 +13,9 @@ "magento/framework": "1.0.0-beta", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-bundle": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/link.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/link.phtml index 3bcab679b7109eb960ed226807fe14fbd87479a9..602b9d2cb13d099756d6639991a6e115b9c3fcb3 100644 --- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/link.phtml +++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/link.phtml @@ -7,4 +7,4 @@ // @codingStandardsIgnoreFile ?> -<a class="action multicheckout" href="<?php /* @escapeNotVerified */ echo $block->getCheckoutUrl()?>" title="<?php /* @escapeNotVerified */ echo __('Check Out with Multiple Addresses');?>"><span><?php /* @escapeNotVerified */ echo __('Check Out with Multiple Addresses');?></span></a> +<a class="action multicheckout" href="<?php /* @escapeNotVerified */ echo $block->getCheckoutUrl()?>"><span><?php /* @escapeNotVerified */ echo __('Check Out with Multiple Addresses');?></span></a> diff --git a/app/code/Magento/OfflineShipping/composer.json b/app/code/Magento/OfflineShipping/composer.json index 8a0287dd1dc6496b18ae5742406cec7799c2a05b..88ac36533cbf5bd62183cb8722ef2613e241dd49 100644 --- a/app/code/Magento/OfflineShipping/composer.json +++ b/app/code/Magento/OfflineShipping/composer.json @@ -11,11 +11,13 @@ "magento/module-sales": "1.0.0-beta", "magento/module-sales-rule": "1.0.0-beta", "magento/module-directory": "1.0.0-beta", - "magento/module-checkout": "1.0.0-beta", "magento/module-quote": "1.0.0-beta", "magento/framework": "1.0.0-beta", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-checkout": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/Payment/Model/Method/AbstractMethod.php b/app/code/Magento/Payment/Model/Method/AbstractMethod.php index 81d0afc88763fe9a1f7ed09a049770ccbfc55767..9d8ba1fffd62c5239ea8735c38a28916d4e67a5f 100644 --- a/app/code/Magento/Payment/Model/Method/AbstractMethod.php +++ b/app/code/Magento/Payment/Model/Method/AbstractMethod.php @@ -58,6 +58,8 @@ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibl const CHECK_ZERO_TOTAL = 'zero_total'; + const GROUP_OFFLINE = 'offline'; + /** * @var string */ diff --git a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml index 3a97da1da871bb7d00a6cde97a8f51e4585ba067..d20275c0e2edc7063740c04575cee395b90844cb 100644 --- a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml +++ b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml @@ -19,8 +19,8 @@ $params = $block->getParams(); <?php elseif (isset($params['error_msg'])): ?> window.top.alert(<?php /* @escapeNotVerified */ echo $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($params['error_msg']) ?>); var require = window.top.require; - require(['jquery'], function($) { - $('#opc-review').trigger('hideAjaxLoader'); + require(['Magento_Checkout/js/model/full-screen-loader'], function(fullScreenLoader) { + fullScreenLoader.stopLoader(); }); <?php elseif (isset($params['order_success'])): ?> window.top.location = "<?php /* @escapeNotVerified */ echo $params['order_success'] ?>"; diff --git a/app/code/Magento/Payment/view/frontend/web/template/payment/cc-form.html b/app/code/Magento/Payment/view/frontend/web/template/payment/cc-form.html index 2164af88215c8b7a34b5a24647f3c2ad2c6d6f42..2df219f3dddf1df4770c7248310c9514682e6f68 100644 --- a/app/code/Magento/Payment/view/frontend/web/template/payment/cc-form.html +++ b/app/code/Magento/Payment/view/frontend/web/template/payment/cc-form.html @@ -11,14 +11,14 @@ <span><!-- ko i18n: 'Credit Card Information'--><!-- /ko --></span> </legend><br /> <!-- /ko --> - <div class="field type required"> - <label data-bind="attr: {for: getCode() + '_cc_type'}" class="label"> - <span><!-- ko i18n: 'Credit Card Type'--><!-- /ko --></span> - </label> + <div class="field type"> <div class="control"> <ul class="credit-card-types"> <!-- ko foreach: {data: getCcAvailableTypesValues(), as: 'item'} --> - <li class="item" data-bind="css: {_active: $parent.selectedCardType() == item.value} "> + <li class="item" data-bind="css: { + _active: $parent.selectedCardType() == item.value, + _inactive: $parent.selectedCardType() != null && $parent.selectedCardType() != item.value + } "> <!--ko if: $parent.getIcons(item.value) --> <img data-bind="attr: { 'src': $parent.getIcons(item.value).url, diff --git a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php index b1b02c4edc780dd51cd991e29eeb0f4353f5b66f..2fba2402a5f6b686f38d2a65e84d68fe782951a5 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php +++ b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php @@ -6,17 +6,20 @@ namespace Magento\Paypal\Controller\Transparent; use Magento\Framework\App\Action\Context; +use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Session\Generic; use Magento\Framework\Session\SessionManager; use Magento\Paypal\Model\Payflow\Service\Request\SecureToken; use Magento\Paypal\Model\Payflow\Transparent; +use Magento\Quote\Model\Quote; /** * Class RequestSecureToken * * @package Magento\Paypal\Controller\Transparent + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class RequestSecureToken extends \Magento\Framework\App\Action\Action { @@ -76,19 +79,43 @@ class RequestSecureToken extends \Magento\Framework\App\Action\Action */ public function execute() { - $this->sessionTransparent->setQuoteId($this->sessionManager->getQuote()->getId()); + /** @var Quote $quote */ + $quote = $this->sessionManager->getQuote(); - $token = $this->secureTokenService->requestToken($this->sessionManager->getQuote()); + if (!$quote or !$quote instanceof Quote) { + return $this->getErrorResponse(); + } - $result = []; - $result[$this->transparent->getCode()]['fields'] = $token->getData(); - $result['success'] = $token->getSecuretoken() ? true : false; + $this->sessionTransparent->setQuoteId($quote->getId()); + try { + $token = $this->secureTokenService->requestToken($quote); + if (!$token->getData('securetoken')) { + throw new \LogicException(); + } - if (!$result['success']) { - $result['error'] = true; - $result['error_messages'] = __('Secure Token Error. Try again.'); + return $this->resultJsonFactory->create()->setData( + [ + $this->transparent->getCode() => ['fields' => $token->getData()], + 'success' => true, + 'error' => false + ] + ); + } catch (\Exception $e) { + return $this->getErrorResponse(); } + } - return $this->resultJsonFactory->create()->setData($result); + /** + * @return Json + */ + private function getErrorResponse() + { + return $this->resultJsonFactory->create()->setData( + [ + 'success' => false, + 'error' => true, + 'error_messages' => __('Your payment has been declined. Please try again.') + ] + ); } } diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php index b1d69d9a4052b0a8bb2ff1bdb6ce266d583f528c..b23574a3654d1e418649676d8537d3fe0515e6fc 100644 --- a/app/code/Magento/Paypal/Model/Config.php +++ b/app/code/Magento/Paypal/Model/Config.php @@ -151,6 +151,12 @@ class Config extends AbstractConfig const EC_BA_SIGNUP_NEVER = 'never'; + /** + * Paypal setting + */ + const TRANSFER_CART_LINE_ITEMS = 'lineItemsEnabled'; + const TRANSFER_SHIPPING_OPTIONS = 'transferShippingOptions'; + /**#@-*/ /** diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index a0bd7721f7bc653d389174456c6eb37c951f2b6a..cdf2ba3e7e58c4079335ab1a72e6aa4ec61f266e 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -12,6 +12,7 @@ use Magento\Paypal\Model\Express\Checkout\Quote as PaypalQuote; use Magento\Sales\Model\Order\Email\Sender\OrderSender; use Magento\Quote\Model\Quote\Address; use Magento\Framework\DataObject; +use Magento\Paypal\Model\Cart as PaypalCart; /** * Wrapper that performs Paypal Express and Checkout communication @@ -529,6 +530,7 @@ class Checkout } // suppress or export shipping address + $address = null; if ($this->_quote->getIsVirtual()) { if ($this->_config->getValue('requireBillingAddress') == PaypalConfig::REQUIRE_BILLING_ADDRESS_VIRTUAL @@ -550,29 +552,13 @@ class Checkout $this->_quote->getPayment()->save(); } - // add line items /** @var $cart \Magento\Payment\Model\Cart */ $cart = $this->_cartFactory->create(['salesModel' => $this->_quote]); - $this->_api->setPaypalCart($cart) - ->setIsLineItemsEnabled($this->_config->getValue('lineItemsEnabled')); - // add shipping options if needed and line items are available - $cartItems = $cart->getAllItems(); - if ($this->_config->getValue('lineItemsEnabled') - && $this->_config->getValue('transferShippingOptions') - && !empty($cartItems) - ) { - if (!$this->_quote->getIsVirtual()) { - $options = $this->_prepareShippingOptions($address, true); - if ($options) { - $this->_api->setShippingOptionsCallbackUrl( - $this->_coreUrl->getUrl( - '*/*/shippingOptionsCallback', - ['quote_id' => $this->_quote->getId()] - ) - )->setShippingOptions($options); - } - } + $this->_api->setPaypalCart($cart); + + if (!$this->_taxData->getConfig()->priceIncludesTax()) { + $this->setShippingOptions($cart, $address); } $this->_config->exportExpressCheckoutStyleSettings($this->_api); @@ -582,8 +568,8 @@ class Checkout $this->_api->callSetExpressCheckout(); $token = $this->_api->getToken(); - $this->_redirectUrl = $button ? $this->_config->getExpressCheckoutStartUrl($token) - : $this->_config->getPayPalBasicStartUrl($token); + + $this->_setRedirectUrl($button, $token); $payment = $this->_quote->getPayment(); $payment->unsAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT); @@ -1169,6 +1155,19 @@ class Checkout return $this; } + /** + * Create payment redirect url + * @param bool|null $button + * @param string $token + * @return void + */ + protected function _setRedirectUrl($button, $token) + { + $this->_redirectUrl = ($button && !$this->_taxData->getConfig()->priceIncludesTax()) + ? $this->_config->getExpressCheckoutStartUrl($token) + : $this->_config->getPayPalBasicStartUrl($token); + } + /** * Get customer session object * @@ -1178,4 +1177,35 @@ class Checkout { return $this->_customerSession; } + + /** + * Set shipping options to api + * @param \Magento\Paypal\Model\Cart $cart + * @param \Magento\Quote\Model\Quote\Address $address + * @return void + */ + private function setShippingOptions(PaypalCart $cart, Address $address) + { + // for included tax always disable line items (related to paypal amount rounding problem) + $this->_api->setIsLineItemsEnabled($this->_config->getValue(PaypalConfig::TRANSFER_CART_LINE_ITEMS)); + + // add shipping options if needed and line items are available + $cartItems = $cart->getAllItems(); + if ($this->_config->getValue(PaypalConfig::TRANSFER_CART_LINE_ITEMS) + && $this->_config->getValue(PaypalConfig::TRANSFER_SHIPPING_OPTIONS) + && !empty($cartItems) + ) { + if (!$this->_quote->getIsVirtual()) { + $options = $this->_prepareShippingOptions($address, true); + if ($options) { + $this->_api->setShippingOptionsCallbackUrl( + $this->_coreUrl->getUrl( + '*/*/shippingOptionsCallback', + ['quote_id' => $this->_quote->getId()] + ) + )->setShippingOptions($options); + } + } + } + } } diff --git a/app/code/Magento/Paypal/Model/Hostedpro.php b/app/code/Magento/Paypal/Model/Hostedpro.php index 369e4464fd839b0bb70c89bc811688a88e476682..421b536d64d945a11bb13abadfb2d1eb226ecd13 100644 --- a/app/code/Magento/Paypal/Model/Hostedpro.php +++ b/app/code/Magento/Paypal/Model/Hostedpro.php @@ -219,7 +219,8 @@ class Hostedpro extends \Magento\Paypal\Model\Direct */ protected function _buildFormUrlRequest(\Magento\Payment\Model\InfoInterface $payment) { - $request = $this->_buildBasicRequest()->setOrder($payment->getOrder())->setPaymentMethod($this); + $order = $payment->getOrder(); + $request = $this->_buildBasicRequest()->setOrder($order)->setPaymentMethod($this)->setAmount($order); return $request; } diff --git a/app/code/Magento/Paypal/Model/Hostedpro/Request.php b/app/code/Magento/Paypal/Model/Hostedpro/Request.php index c1215777eb49b7f1ddff8f58c8957402100ee0fd..545180ac86e6dc5cc767ac62eb3f6ed92ad2cce2 100644 --- a/app/code/Magento/Paypal/Model/Hostedpro/Request.php +++ b/app/code/Magento/Paypal/Model/Hostedpro/Request.php @@ -5,6 +5,8 @@ */ namespace Magento\Paypal\Model\Hostedpro; +use \Magento\Sales\Model\Order; + /** * Website Payments Pro Hosted Solution request model to get token. * @@ -47,6 +49,13 @@ class Request extends \Magento\Framework\DataObject */ protected $_customerAddress = null; + /** + * Tax data + * + * @var \Magento\Tax\Helper\Data + */ + protected $_taxData; + /** * Locale Resolver * @@ -57,15 +66,18 @@ class Request extends \Magento\Framework\DataObject /** * @param \Magento\Framework\Locale\Resolver $localeResolver * @param \Magento\Customer\Helper\Address $customerAddress + * @param \Magento\Tax\Helper\Data $taxData * @param array $data */ public function __construct( \Magento\Framework\Locale\Resolver $localeResolver, \Magento\Customer\Helper\Address $customerAddress, + \Magento\Tax\Helper\Data $taxData, array $data = [] ) { $this->_customerAddress = $customerAddress; $this->localeResolver = $localeResolver; + $this->_taxData = $taxData; parent::__construct($data); } @@ -125,6 +137,71 @@ class Request extends \Magento\Framework\DataObject return $this; } + /** + * Add amount data to request + * + * @access public + * @param \Magento\Sales\Model\Order $order + * @return $this + */ + public function setAmount(Order $order) + { + $this->addData($this->_getAmountData($order)); + return $this; + } + + /** + * Calculate amount for order + * @param \Magento\Sales\Model\Order $order + * @return array + * @throws \Exception + */ + protected function _getAmountData(Order $order) + { + // if tax is included - need add to request only total amount + if ($this->_taxData->getConfig()->priceIncludesTax()) { + return $this->getTaxableAmount($order); + } else { + return $this->getNonTaxableAmount($order); + } + } + + /** + * Get payment amount data with excluded tax + * @param \Magento\Sales\Model\Order $order + * @return array + */ + private function getNonTaxableAmount(Order $order) + { + return [ + 'subtotal' => $this->_formatPrice( + $this->_formatPrice( + $order->getPayment()->getBaseAmountAuthorized() + ) - $this->_formatPrice( + $order->getBaseTaxAmount() + ) - $this->_formatPrice( + $order->getBaseShippingAmount() + ) + ), + 'tax' => $this->_formatPrice($order->getBaseTaxAmount()), + 'shipping' => $this->_formatPrice($order->getBaseShippingAmount()), + ]; + } + + /** + * Get order amount data with included tax + * @param \Magento\Sales\Model\Order $order + * @return array + */ + private function getTaxableAmount(Order $order) + { + $amount = $this->_formatPrice($order->getPayment()->getBaseAmountAuthorized()); + return [ + 'amount' => $amount, + 'subtotal' => $amount // subtotal always is required + ]; + } + /** * Get payment request data as array * @@ -161,17 +238,6 @@ class Request extends \Magento\Framework\DataObject protected function _getOrderData(\Magento\Sales\Model\Order $order) { $request = [ - 'subtotal' => $this->_formatPrice( - $this->_formatPrice( - $order->getPayment()->getBaseAmountAuthorized() - ) - $this->_formatPrice( - $order->getBaseTaxAmount() - ) - $this->_formatPrice( - $order->getBaseShippingAmount() - ) - ), - 'tax' => $this->_formatPrice($order->getBaseTaxAmount()), - 'shipping' => $this->_formatPrice($order->getBaseShippingAmount()), 'invoice' => $order->getIncrementId(), 'address_override' => 'true', 'currency_code' => $order->getBaseCurrencyCode(), diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php index b325255b318640138323ac6a0c520ad9ac3d6fe6..405b02d7d8a8f3ad6fd1910cb48aec8220718f07 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php @@ -54,7 +54,8 @@ class SecureToken * * @param Quote $quote * - * @return Object + * @return DataObject + * @throws \Exception */ public function requestToken(Quote $quote) { diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php index 23fcfae2d163cbcb1c16e63bdb9ab961f813ca6b..ccd9d508392d523064b9448ddc6ce35a74148321 100644 --- a/app/code/Magento/Paypal/Model/Payflowpro.php +++ b/app/code/Magento/Paypal/Model/Payflowpro.php @@ -608,7 +608,7 @@ class Payflowpro extends \Magento\Payment\Model\Method\Cc implements GatewayInte /** * Return request object with basic information for gateway request * - * @return Object + * @return DataObject */ public function buildBasicRequest() { diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php index 6aeccbb1bb42f832b7850262394a0bc0d5300063..7285064bcb799660efe6bbabb99bb02b2fb69073 100644 --- a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php @@ -98,32 +98,29 @@ class RequestSecureTokenTest extends \PHPUnit_Framework_TestCase ); } - /** - * Run test execute method - * - * @param array $result - * @param array $resultExpectation - * - * @dataProvider executeDataProvider - */ - public function testExecute(array $result, array $resultExpectation) + public function testExecuteSuccess() { $quoteId = 99; + $tokenFields = ['fields-1', 'fields-2', 'fields-3']; + $secureToken = 'token_hash'; + $resultExpectation = [ + 'transparent' => [ + 'fields' => ['fields-1', 'fields-2', 'fields-3'] + ], + 'success' => true, + 'error' => false + ]; $quoteMock = $this->getMockBuilder('Magento\Quote\Model\Quote') ->disableOriginalConstructor() ->getMock(); $tokenMock = $this->getMockBuilder('Magento\Framework\DataObject') - ->setMethods(['getData', 'getSecuretoken']) ->disableOriginalConstructor() ->getMock(); $jsonMock = $this->getMockBuilder('Magento\Framework\Controller\Result\Json') ->disableOriginalConstructor() ->getMock(); - $this->transparentMock->expects($this->once()) - ->method('getCode') - ->willReturn('transparent'); $this->sessionManagerMock->expects($this->atLeastOnce()) ->method('getQuote') ->willReturn($quoteMock); @@ -140,12 +137,14 @@ class RequestSecureTokenTest extends \PHPUnit_Framework_TestCase $this->transparentMock->expects($this->once()) ->method('getCode') ->willReturn('transparent'); - $tokenMock->expects($this->once()) + $tokenMock->expects($this->atLeastOnce()) ->method('getData') - ->willReturn($result['transparent']['fields']); - $tokenMock->expects($this->once()) - ->method('getSecuretoken') - ->willReturn($result['success']); + ->willReturnMap( + [ + ['', null, $tokenFields], + ['securetoken', null, $secureToken] + ] + ); $this->resultJsonFactoryMock->expects($this->once()) ->method('create') ->willReturn($jsonMock); @@ -157,42 +156,70 @@ class RequestSecureTokenTest extends \PHPUnit_Framework_TestCase $this->assertEquals($jsonMock, $this->controller->execute()); } - /** - * @return array - */ - public function executeDataProvider() + public function testExecuteTokenRequestException() { - return [ - [ - 'result' => [ - 'transparent' => [ - 'fields' => ['fields-1', 'fields-2', 'fields-3'] - ], - 'success' => 1 - ], - 'result_expectation' => [ - 'transparent' => [ - 'fields' => ['fields-1', 'fields-2', 'fields-3'] - ], - 'success' => true - ] - ], - [ - 'result' => [ - 'transparent' => [ - 'fields' => ['fields-1', 'fields-2', 'fields-3'] - ], - 'success' => null, - ], - 'result_expectation' => [ - 'transparent' => [ - 'fields' => ['fields-1', 'fields-2', 'fields-3'] - ], - 'success' => false, - 'error' => true, - 'error_messages' => __('Secure Token Error. Try again.') - ] - ] + $quoteId = 99; + $resultExpectation = [ + 'success' => false, + 'error' => true, + 'error_messages' => __('Your payment has been declined. Please try again.') + ]; + + $quoteMock = $this->getMockBuilder('Magento\Quote\Model\Quote') + ->disableOriginalConstructor() + ->getMock(); + $jsonMock = $this->getMockBuilder('Magento\Framework\Controller\Result\Json') + ->disableOriginalConstructor() + ->getMock(); + + $this->sessionManagerMock->expects($this->atLeastOnce()) + ->method('getQuote') + ->willReturn($quoteMock); + $quoteMock->expects($this->once()) + ->method('getId') + ->willReturn($quoteId); + $this->sessionTransparentMock->expects($this->once()) + ->method('setQuoteId') + ->with($quoteId); + $this->secureTokenServiceMock->expects($this->once()) + ->method('requestToken') + ->with($quoteMock) + ->willThrowException(new \Exception()); + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($jsonMock); + $jsonMock->expects($this->once()) + ->method('setData') + ->with($resultExpectation) + ->willReturnSelf(); + + $this->assertEquals($jsonMock, $this->controller->execute()); + } + + public function testExecuteEmptyQuoteError() + { + $resultExpectation = [ + 'success' => false, + 'error' => true, + 'error_messages' => __('Your payment has been declined. Please try again.') ]; + + $quoteMock = null; + $jsonMock = $this->getMockBuilder('Magento\Framework\Controller\Result\Json') + ->disableOriginalConstructor() + ->getMock(); + + $this->sessionManagerMock->expects($this->atLeastOnce()) + ->method('getQuote') + ->willReturn($quoteMock); + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($jsonMock); + $jsonMock->expects($this->once()) + ->method('setData') + ->with($resultExpectation) + ->willReturnSelf(); + + $this->assertEquals($jsonMock, $this->controller->execute()); } } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Hostedpro/RequestTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Hostedpro/RequestTest.php index 0638c446d7dc01a51c8038723813e1e6b4625d9e..6e3d37ec21ba6704fbcde095077fe8d6c3e5362d 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Hostedpro/RequestTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Hostedpro/RequestTest.php @@ -7,6 +7,11 @@ namespace Magento\Paypal\Test\Unit\Model\Hostedpro; class RequestTest extends \PHPUnit_Framework_TestCase { + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $helper; + /** * @var \Magento\Paypal\Model\Hostedpro\Request */ @@ -14,18 +19,26 @@ class RequestTest extends \PHPUnit_Framework_TestCase protected $localeResolverMock; + /** + * @var \Magento\Tax\Helper\Data + */ + protected $taxData; + protected function setUp() { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->localeResolverMock = $this->getMockBuilder('Magento\Framework\Locale\Resolver') ->disableOriginalConstructor() ->getMock(); - $this->_model = $helper->getObject( + $this->taxData = $this->helper->getObject('Magento\Tax\Helper\Data'); + + $this->_model = $this->helper->getObject( 'Magento\Paypal\Model\Hostedpro\Request', [ - 'localeResolver' => $this->localeResolverMock + 'localeResolver' => $this->localeResolverMock, + 'taxData' => $this->taxData ] ); } @@ -134,4 +147,130 @@ class RequestTest extends \PHPUnit_Framework_TestCase $this->assertEquals('US', $this->_model->getData('lc')); $this->assertEquals($expectedData, $this->_model->getData()); } + + /** + * @covers \Magento\Paypal\Model\Hostedpro\Request::setOrder + */ + public function testSetOrder() + { + $expectation = [ + 'invoice' => '#000001', + 'address_override' => 'true', + 'currency_code' => 'USD', + 'buyer_email' => 'buyer@email.com', + ]; + + $order = $this->getMockBuilder('Magento\Sales\Model\Order') + ->disableOriginalConstructor() + ->getMock(); + + $order->expects(static::once()) + ->method('getIncrementId') + ->willReturn($expectation['invoice']); + + $order->expects(static::once()) + ->method('getBaseCurrencyCode') + ->willReturn($expectation['currency_code']); + + $order->expects(static::once()) + ->method('getCustomerEmail') + ->willReturn($expectation['buyer_email']); + + $this->_model->setOrder($order); + static::assertEquals($expectation, $this->_model->getData()); + } + + /** + * @covers \Magento\Paypal\Model\Hostedpro\Request::setAmount + */ + public function testSetAmountWithoutTax() + { + $expectation = [ + 'subtotal' => 12.04, + 'tax' => 2.03, + 'shipping' => 5.05 + ]; + $amount = array_sum($expectation); + + static::assertFalse($this->taxData->priceIncludesTax()); + + $payment = $this->getMockBuilder('Magento\Sales\Model\Order\Payment') + ->disableOriginalConstructor() + ->getMock(); + + $order = $this->getMockBuilder('Magento\Sales\Model\Order') + ->disableOriginalConstructor() + ->getMock(); + + $payment->expects(static::once()) + ->method('getBaseAmountAuthorized') + ->willReturn($amount); + + $order->expects(static::once()) + ->method('getPayment') + ->willReturn($payment); + + $order->expects(static::atLeastOnce()) + ->method('getBaseTaxAmount') + ->willReturn($expectation['tax']); + + $order->expects(static::atLeastOnce()) + ->method('getBaseShippingAmount') + ->willReturn($expectation['shipping']); + + $this->_model->setAmount($order); + + static::assertEquals($expectation, $this->_model->getData()); + } + + public function testSetAmountWithIncludedTax() + { + /** @var \Magento\Tax\Model\Config $config */ + $config = $this->helper->getObject('Magento\Tax\Model\Config'); + $config->setPriceIncludesTax(true); + + $this->taxData = $this->helper->getObject( + 'Magento\Tax\Helper\Data', + [ + 'taxConfig' => $config + ] + ); + + $this->_model = $this->helper->getObject( + 'Magento\Paypal\Model\Hostedpro\Request', + [ + 'localeResolver' => $this->localeResolverMock, + 'taxData' => $this->taxData + ] + ); + + static::assertTrue($this->taxData->getConfig()->priceIncludesTax()); + + $amount = 19.65; + + $expectation = [ + 'amount' => $amount, + 'subtotal' => $amount + ]; + + $payment = $this->getMockBuilder('Magento\Sales\Model\Order\Payment') + ->disableOriginalConstructor() + ->getMock(); + + $order = $this->getMockBuilder('Magento\Sales\Model\Order') + ->disableOriginalConstructor() + ->getMock(); + + $payment->expects(static::once()) + ->method('getBaseAmountAuthorized') + ->willReturn($amount); + + $order->expects(static::once()) + ->method('getPayment') + ->willReturn($payment); + + $this->_model->setAmount($order); + + static::assertEquals($expectation, $this->_model->getData()); + } } diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml index 65ec357646179c5a1910a008c7bfd754154baec4..6486f25d5f6af9cdd7f9574fc2d72ca2ded28fb1 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml @@ -92,6 +92,10 @@ </predicate> </event> </events> + <events selector="tr:has(> td > [data-enable='payment']) [type='checkbox']"> + <event value="1" name="usedefault-on"/> + <event value="0" name="usedefault-off"/> + </events> <events selector="[data-enable='bml']"> <event value="0" name="deactivate-bml"/> <event value="1" name="activate-bml"/> @@ -108,6 +112,8 @@ <relation target="payflow_link_us"> <rule type="disable" event="activate-rule"/> <rule type="payflow/express/disable-conditional" event="deactivate-rule"/> + <rule type="payflow/express/usedefault-disable" event="usedefault-off"/> + <rule type="payflow/express/usedefault-enable" event="usedefault-on"/> <rule type="payflow/bml/disable-conditional" event="deactivate-rule"/> <rule type="payflow/bml/enable-conditional" event="activate-bml"> <argument name="wpp_usuk">wpp_usuk</argument> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system.xml b/app/code/Magento/Paypal/etc/adminhtml/system.xml index 60a7d934e47a5843b307338c8e40f2317fa8a000..f79237b9e30615071bcc1630aea7673b02f43030 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system.xml @@ -8,11 +8,11 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Config/etc/system_file.xsd"> <system> <section id="payment"> - <group id="paypal_notice" translate="label" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="paypal_notice" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1"> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> <help_url>https://www.paypal-marketing.com/emarketing/partner/na/merchantlineup/home.page#mainTab=checkoutlineup</help_url> </group> - <group id="account" translate="label" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="account" translate="label" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Merchant Location</label> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> <field id="merchant_country" type="select" translate="label comment" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0"> diff --git a/app/code/Magento/Paypal/view/adminhtml/web/js/rules/payflow/express/usedefault-disable.js b/app/code/Magento/Paypal/view/adminhtml/web/js/rules/payflow/express/usedefault-disable.js new file mode 100644 index 0000000000000000000000000000000000000000..f217b55ecbbb9d2a28d97d875cc5b584fc89ec51 --- /dev/null +++ b/app/code/Magento/Paypal/view/adminhtml/web/js/rules/payflow/express/usedefault-disable.js @@ -0,0 +1,17 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Paypal/js/rules/payflow/express/enable' +], function (enableExpress) { + 'use strict'; + + return function ($target, $owner, data) { + + $target.find('input[id="' + $target.find(data.enableExpress).attr('id') + '_inherit"]').prop('checked', false); + enableExpress($target, $owner, data); + $target.find(data.enableExpress).change(); + }; +}); + diff --git a/app/code/Magento/Paypal/view/adminhtml/web/js/rules/payflow/express/usedefault-enable.js b/app/code/Magento/Paypal/view/adminhtml/web/js/rules/payflow/express/usedefault-enable.js new file mode 100644 index 0000000000000000000000000000000000000000..9993fb21c55fe44c05b7ce7e27f0a2cbff3be8c2 --- /dev/null +++ b/app/code/Magento/Paypal/view/adminhtml/web/js/rules/payflow/express/usedefault-enable.js @@ -0,0 +1,16 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Paypal/js/rules/payflow/express/disable' +], function (disableExpress) { + 'use strict'; + + return function ($target, $owner, data) { + + $target.find('input[id="' + $target.find(data.enableExpress).attr('id') + '_inherit"]').prop('checked', true); + disableExpress($target, $owner, data); + $target.find(data.enableExpress).change(); + }; +}); diff --git a/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js b/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js index a5a98e899e9d46ac3d07c66f28990226468900e2..3ff36adf61426bbbd23d77fbe559d22c581d82d9 100644 --- a/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js +++ b/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js @@ -64,7 +64,7 @@ define([ _.each(events, function (elementEvent, name) { var predicate = elementEvent.predicate, result = true; - if ($(this).val() === elementEvent.value) { + if (solution.getValue($(this)) === elementEvent.value) { if (predicate.name) { require([ 'Magento_Paypal/js/predicate/' + predicate.name @@ -85,6 +85,13 @@ define([ }, this); return this; }, + + getValue: function ($element) { + if ($element.is(':checkbox')) { + return $element.prop('checked') ? '1' : '0'; + } + return $element.val(); + }, /** * Adding event listeners */ diff --git a/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js b/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js index 819f8cea3e4e1ca269b0b1b72030a03a32d46d94..c85e58c0de76161888668680a4d815ffb4b074d4 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js @@ -9,9 +9,10 @@ define( 'Magento_Checkout/js/model/url-builder', 'mage/storage', 'Magento_Checkout/js/model/error-processor', - 'Magento_Customer/js/model/customer' + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/model/full-screen-loader' ], - function ($, quote, urlBuilder, storage, errorProcessor, customer) { + function ($, quote, urlBuilder, storage, errorProcessor, customer, fullScreenLoader) { 'use strict'; return function (messageContainer) { @@ -37,8 +38,9 @@ define( method: paymentData }; } + fullScreenLoader.startLoader(); return storage.put( - serviceUrl, JSON.stringify(payload) + serviceUrl, JSON.stringify(payload), false ).done( function () { $.mage.redirect(window.checkoutConfig.payment.paypalExpress.redirectUrl[quote.paymentMethod().method]); @@ -46,6 +48,7 @@ define( ).fail( function (response) { errorProcessor.process(response, messageContainer); + fullScreenLoader.stopLoader(); } ); }; diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js index ab13dd5647ae08099f3dbbdfdf2b256adcd0eee7..7820ef00544c2660c2083b32dc16ad28e3899d21 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js @@ -6,9 +6,10 @@ define( [ 'Magento_Checkout/js/view/payment/default', 'ko', - 'Magento_Paypal/js/model/iframe' + 'Magento_Paypal/js/model/iframe', + 'Magento_Checkout/js/model/full-screen-loader' ], - function (Component, ko, iframe) { + function (Component, ko, iframe, fullScreenLoader) { 'use strict'; return Component.extend({ @@ -47,6 +48,13 @@ define( // capture all click events document.addEventListener('click', iframe.stopEventPropagation, true); } + }, + /** + * Hide loader when iframe is fully loaded. + * @returns {void} + */ + iframeLoaded: function() { + fullScreenLoader.stopLoader(); } }); } diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro-method.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro-method.js index eda3532e9af36b15c54fe941c506dea4e4102fbb..6c4f4da1a8b75c06ecd136bfb5774491e5e4f75d 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro-method.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro-method.js @@ -48,7 +48,7 @@ define( var self = this; if (this.validateHandler() && additionalValidators.validate()) { this.isPlaceOrderActionAllowed(false); - $.when(setPaymentInformationAction(this.messageContainer)).done(function() { + $.when(setPaymentInformationAction(this.messageContainer, {'method': self.getCode()})).done(function() { self.placeOrderHandler(); }).fail(function() { self.isPlaceOrderActionAllowed(true); diff --git a/app/code/Magento/Paypal/view/frontend/web/template/payment/iframe-methods.html b/app/code/Magento/Paypal/view/frontend/web/template/payment/iframe-methods.html index 2653590cee4f84e4ddd05e51ffce60923859f50e..711ef49cf9397658f3e56eeeca73617c0c70263c 100644 --- a/app/code/Magento/Paypal/view/frontend/web/template/payment/iframe-methods.html +++ b/app/code/Magento/Paypal/view/frontend/web/template/payment/iframe-methods.html @@ -41,7 +41,7 @@ <div><!-- ko i18n: 'Please do not refresh the page until you complete payment.' --><!-- /ko --></div> </div> <!-- ko if: isPaymentReady() --> - <iframe data-bind="attr: {id: getCode() + '-iframe', src: getActionUrl()}" + <iframe data-bind="attr: {id: getCode() + '-iframe', src: getActionUrl()}, event: {load: iframeLoaded}" data-container="paypal-iframe" class="paypal iframe" scrolling="no" diff --git a/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php b/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php new file mode 100644 index 0000000000000000000000000000000000000000..fe272f0b574ffcdab1c1b23a8c99dd7253936843 --- /dev/null +++ b/app/code/Magento/ProductVideo/Block/Adminhtml/Product/Edit/NewVideo.php @@ -0,0 +1,240 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ProductVideo\Block\Adminhtml\Product\Edit; + +/** + * @SuppressWarnings(PHPMD.DepthOfInheritance) + */ +class NewVideo extends \Magento\Backend\Block\Widget\Form\Generic +{ + /** + * @var \Magento\Framework\Json\EncoderInterface + */ + protected $jsonEncoder; + + /** + * @param \Magento\Backend\Block\Template\Context $context + * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder + * @param \Magento\Framework\Registry $registry + * @param \Magento\Framework\Data\FormFactory $formFactory + * @param array $data + */ + public function __construct( + \Magento\Backend\Block\Template\Context $context, + \Magento\Framework\Registry $registry, + \Magento\Framework\Data\FormFactory $formFactory, + \Magento\Framework\Json\EncoderInterface $jsonEncoder, + array $data = [] + ) { + parent::__construct($context, $registry, $formFactory, $data); + $this->jsonEncoder = $jsonEncoder; + $this->setUseContainer(true); + } + + /** + * Form preparation + * + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function _prepareForm() + { + /** @var \Magento\Framework\Data\Form $form */ + $form = $this->_formFactory->create([ + 'data' => [ + 'id' => 'new_video_form', + 'class' => 'admin__scope-old', + 'enctype' => 'multipart/form-data', + ] + ]); + $form->setUseContainer($this->getUseContainer()); + + $form->addField('new_video_messages', 'note', []); + + $fieldset = $form->addFieldset('new_video_form_fieldset', []); + + $fieldset->addField( + '', + 'hidden', + [ + 'name' => 'form_key', + 'value' => $this->getFormKey(), + ] + ); + + $fieldset->addField( + 'item_id', + 'hidden', + [] + ); + + $fieldset->addField( + 'file_name', + 'hidden', + [] + ); + + $fieldset->addField( + 'video_provider', + 'hidden', + [ + 'name' => 'video_provider', + ] + ); + + $fieldset->addField( + 'video_url', + 'text', + [ + 'class' => 'edited-data validate-url', + 'label' => __('Url'), + 'title' => __('Url'), + 'required' => true, + 'name' => 'video_url', + 'note' => 'Youtube or Vimeo supported', + ] + ); + + + $fieldset->addField( + 'video_title', + 'text', + [ + 'class' => 'edited-data', + 'label' => __('Title'), + 'title' => __('Title'), + 'required' => true, + 'name' => 'video_title', + ] + ); + + $fieldset->addField( + 'video_description', + 'textarea', + [ + 'class' => 'edited-data', + 'label' => __('Description'), + 'title' => __('Description'), + 'name' => 'video_description', + ] + ); + + $fieldset->addField( + 'new_video_screenshot', + 'file', + [ + 'label' => __('Preview Image'), + 'title' => __('Preview Image'), + 'name' => 'image', + ] + ); + + $fieldset->addField( + 'new_video_screenshot_preview', + 'button', + [ + 'class' => 'preview_hidden_image_input_button', + 'label' => '', + 'name' => '_preview', + ] + ); + + + $fieldset->addField( + 'new_video_get', + 'button', + [ + 'label' => '', + 'title' => __('Get Video Information'), + 'name' => 'new_video_get', + 'value' => 'Get Video Information', + ] + ); + + $fieldset->addField( + 'video_base_image', + 'checkbox', + [ + 'class' => 'video_image_role', + 'label' => 'Base Image', + 'title' => __('Base Image'), + 'data-role' => 'role-type-selector', + 'value' => 'image', + ] + ); + + $fieldset->addField( + 'video_small_image', + 'checkbox', + [ + 'class' => 'video_image_role', + 'label' => 'Small Image', + 'title' => __('Small Image'), + 'data-role' => 'role-type-selector', + 'value' => 'small_image', + ] + ); + + $fieldset->addField( + 'video_thumb_image', + 'checkbox', + [ + 'class' => 'video_image_role', + 'label' => 'Thumbnail', + 'title' => __('Thumbnail'), + 'data-role' => 'role-type-selector', + 'value' => 'thumbnail', + ] + ); + + $fieldset->addField( + 'video_swatch_image', + 'checkbox', + [ + 'class' => 'video_image_role', + 'label' => 'Swatch Image', + 'title' => __('Swatch Image'), + 'data-role' => 'role-type-selector', + 'value' => 'swatch_image', + ] + ); + + $fieldset->addField( + 'new_video_disabled', + 'checkbox', + [ + 'class' => 'edited-data', + 'label' => 'Hide from Product Page', + 'title' => __('Hide from Product Page'), + 'name' => 'disabled', + ] + ); + + $this->setForm($form); + } + + public function getHtmlId() + { + if (null === $this->getData('id')) { + $this->setData('id', $this->mathRandom->getUniqueHash('id_')); + } + return $this->getData('id'); + } + + /** + * @return string + */ + public function getWidgetOptions() + { + return $this->jsonEncoder->encode( + [ + 'saveVideoUrl' => $this->getUrl('catalog/product_gallery/upload'), + 'saveRemoteVideoUrl' => $this->getUrl('product_video/product_gallery/retrieveImage'), + 'htmlId' => $this->getHtmlId(), + ] + ); + } +} diff --git a/app/code/Magento/ProductVideo/LICENSE.txt b/app/code/Magento/ProductVideo/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..36b2459f6aa63e48826b67ee371c886ac2120169 --- /dev/null +++ b/app/code/Magento/ProductVideo/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. diff --git a/app/code/Magento/ProductVideo/LICENSE_AFL.txt b/app/code/Magento/ProductVideo/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/ProductVideo/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/ProductVideo/Model/Observer.php b/app/code/Magento/ProductVideo/Model/Observer.php new file mode 100644 index 0000000000000000000000000000000000000000..286b77edc914eec938e483e8e575b1797f0b2374 --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/Observer.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Model; + + +class Observer +{ + /** + * @param mixed $observer + * @return void + */ + public function changeTemplate($observer) + { + $observer->getBlock()->setTemplate('Magento_ProductVideo::helper/gallery.phtml'); + } +} diff --git a/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php b/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php new file mode 100644 index 0000000000000000000000000000000000000000..809da5a9c6b3dea4a707c5e37eaac92a9e58092a --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/Plugin/BaseImage.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Model\Plugin; + +use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\BaseImage as OriginalBloc; +use Magento\Framework\View\Element\Template; + +/** + * BaseImage decorator + */ +class BaseImage +{ + /** + * Element output template + */ + const ELEMENT_OUTPUT_TEMPLATE = 'Magento_ProductVideo::product/edit/base_image.phtml'; + + /** + * @param OriginalBloc $baseImage + * @param Template $block + * @return Template + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAssignBlockVariables(OriginalBloc $baseImage, Template $block) + { + $block->assign([ + 'videoPlaceholderText' => __('Click here to add videos.'), + 'addVideoTitle' => __('New Video'), + ]); + + return $block; + } + + /** + * @param OriginalBloc $baseImage + * @param Template $block + * @return Template + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterCreateElementHtmlOutputBlock(OriginalBloc $baseImage, Template $block) + { + $block->setTemplate(self::ELEMENT_OUTPUT_TEMPLATE); + return $block; + } +} diff --git a/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php b/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..f5b73bf4050d7a2aa34514d44e8f6bcc0b61af05 --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/Plugin/ExternalVideoEntryProcessor.php @@ -0,0 +1,430 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Model\Plugin; + +use Magento\Customer\Model\Resource\Form\Attribute; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Catalog\Model\Product; +use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; +use Magento\Catalog\Model\Product\Attribute\Backend\Media as MediaBackendModel; +use Magento\ProductVideo\Setup\InstallSchema; + +/** + * Class External video entry processor + */ +class ExternalVideoEntryProcessor +{ + /** + * Key to store additional data from other stores + */ + const ADDITIONAL_STORE_DATA_KEY = 'additional_store_data'; + + /** + * @var array + */ + protected $videoPropertiesDbMapping = [ + 'value_id' => 'value_id', + 'store_id' => 'store_id', + 'video_provider' => 'provider', + 'video_url' => 'url', + 'video_title' => 'title', + 'video_description' => 'description', + 'video_metadata' => 'metadata' + ]; + + /** + * @var \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media + */ + protected $resourceEntryMediaGallery; + + /** + * @param \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media $resourceEntryMediaGallery + */ + public function __construct( + \Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media $resourceEntryMediaGallery + ) { + $this->resourceEntryMediaGallery = $resourceEntryMediaGallery; + } + + /** + * @param MediaBackendModel $mediaBackendModel + * @param Product $product + * @return Product + */ + public function afterAfterLoad(MediaBackendModel $mediaBackendModel, Product $product) + { + $mediaCollection = $this->getMediaEntriesDataCollection($product, $mediaBackendModel->getAttribute()); + if (!empty($mediaCollection)) { + $ids = $this->collectVideoEntriesIds($mediaCollection); + $videoDataCollection = $this->loadVideoDataById($ids, $product->getStoreId()); + $mediaEntriesDataCollection = $this->addVideoDataToMediaEntries($mediaCollection, $videoDataCollection); + $product->setData($mediaBackendModel->getAttribute()->getAttributeCode(), $mediaEntriesDataCollection); + } + + return $product; + } + + /** + * @param MediaBackendModel $mediaBackendModel + * @param Product $product + * @return Product + */ + public function afterBeforeSave(MediaBackendModel $mediaBackendModel, Product $product) + { + $attribute = $mediaBackendModel->getAttribute(); + $mediaCollection = $this->getMediaEntriesDataCollection($product, $attribute); + if (!empty($mediaCollection)) { + $storeDataCollection = $this->loadStoreViewVideoData($mediaCollection, $product->getStoreId()); + $mediaCollection = $this->addAdditionalStoreData($mediaCollection, $storeDataCollection); + $product->setData( + $attribute->getAttributeCode(), + $mediaCollection + $product->getData($attribute->getAttributeCode()) + ); + } + + return $product; + } + + /** + * @param MediaBackendModel $mediaBackendModel + * @param Product $product + * @return Product + */ + public function afterAfterSave(MediaBackendModel $mediaBackendModel, Product $product) + { + $mediaCollection = $this->getMediaEntriesDataCollection($product, $mediaBackendModel->getAttribute()); + if (!empty($mediaCollection)) { + $videoDataCollection = $this->collectVideoData($mediaCollection); + $this->saveVideoData($videoDataCollection, $product->getStoreId()); + $this->saveAdditionalStoreData($videoDataCollection); + } + + return $product; + } + + /** + * @param array $videoDataCollection + * @param int $storeId + * @return void + */ + protected function saveVideoData(array $videoDataCollection, $storeId) + { + foreach ($videoDataCollection as $item) { + $item['store_id'] = $storeId; + $this->saveVideoValuesItem($item); + } + } + + /** + * @param array $videoDataCollection + * @return void + */ + protected function saveAdditionalStoreData(array $videoDataCollection) + { + foreach ($videoDataCollection as $mediaItem) { + if (!empty($mediaItem[self::ADDITIONAL_STORE_DATA_KEY])) { + foreach ($mediaItem[self::ADDITIONAL_STORE_DATA_KEY] as $additionalStoreItem) { + $additionalStoreItem['value_id'] = $mediaItem['value_id']; + $this->saveVideoValuesItem($additionalStoreItem); + } + } + } + } + + /** + * @param array $item + * @return void + */ + protected function saveVideoValuesItem(array $item) + { + $this->resourceEntryMediaGallery->saveDataRow( + InstallSchema::GALLERY_VALUE_VIDEO_TABLE, + $this->prepareVideoRowDataForSave($item) + ); + } + + /** + * @param array $mediaCollection + * @param int $currentStoreId + * @return array + */ + protected function excludeCurrentStoreRecord(array $mediaCollection, $currentStoreId) + { + return array_filter( + $mediaCollection, + function ($item) use ($currentStoreId) { + return $item['store_id'] == $currentStoreId ? false : true; + } + ); + } + + /** + * @param array $rowData + * @return array + */ + protected function prepareVideoRowDataForSave(array $rowData) + { + foreach ($this->videoPropertiesDbMapping as $sourceKey => $dbKey) { + if (array_key_exists($sourceKey, $rowData) && $sourceKey != $dbKey) { + $rowData[$dbKey] = $rowData[$sourceKey]; + unset($rowData[$sourceKey]); + } + } + $rowData = array_intersect_key($rowData, array_flip($this->videoPropertiesDbMapping)); + + return $rowData; + } + + /** + * @param array $mediaCollection + * @param int $excludedStore + * @return array + */ + protected function loadStoreViewVideoData(array $mediaCollection, $excludedStore) + { + $ids = $this->collectVideoEntriesIdsToAdditionalLoad($mediaCollection); + $result = []; + if (!empty($ids)) { + $result = $this->resourceEntryMediaGallery->loadDataFromTableByValueId( + InstallSchema::GALLERY_VALUE_VIDEO_TABLE, + $ids, + null, + $this->videoPropertiesDbMapping + ); + $result = $this->excludeCurrentStoreRecord($result, $excludedStore); + } + + return $result; + } + + /** + * @param array $mediaCollection + * @return array + */ + protected function collectVideoData(array $mediaCollection) + { + $videoDataCollection = []; + foreach ($mediaCollection as $item) { + if (!empty($item['media_type']) + && empty($item['removed']) + && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE + ) { + $videoData = $this->extractVideoDataFromRowData($item); + $videoDataCollection[] = $videoData; + } + } + + return $videoDataCollection; + } + + /** + * @param array $rowData + * @return array + */ + protected function extractVideoDataFromRowData(array $rowData) + { + return array_intersect_key( + $rowData, + array_merge($this->videoPropertiesDbMapping, [self::ADDITIONAL_STORE_DATA_KEY => '']) + ); + } + + /** + * @param Product $product + * @param AbstractAttribute $attribute + * @return array + */ + protected function getMediaEntriesDataCollection(Product $product, AbstractAttribute $attribute) + { + $attributeCode = $attribute->getAttributeCode(); + $mediaData = $product->getData($attributeCode); + if (!empty($mediaData['images']) && is_array($mediaData['images'])) { + return $mediaData['images']; + } + return []; + } + + /** + * @param array $mediaCollection + * @return array + */ + protected function collectVideoEntriesIds(array $mediaCollection) + { + $ids = []; + foreach ($mediaCollection as $item) { + if ($item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE) { + $ids[] = $item['value_id']; + } + } + return $ids; + } + + /** + * @param array $mediaCollection + * @return array + */ + protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection) + { + $ids = []; + foreach ($mediaCollection as $item) { + if (!empty($item['media_type']) + && empty($item['removed']) + && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE + && isset($item['save_data_from']) + ) { + $ids[] = $item['save_data_from']; + } + } + return $ids; + } + + /** + * @param array $ids + * @param int $storeId + * @return array + */ + protected function loadVideoDataById(array $ids, $storeId = null) + { + $mainTableAlias = $this->resourceEntryMediaGallery->getMainTableAlias(); + $joinConditions = $mainTableAlias.'.value_id = store_value.value_id'; + if (null !== $storeId) { + $joinConditions = implode( + ' AND ', + [ + $joinConditions, + 'store_value.store_id = ' . $storeId + ] + ); + } + $joinTable = [ + [ + ['store_value' => $this->resourceEntryMediaGallery->getTable(InstallSchema::GALLERY_VALUE_VIDEO_TABLE)], + $joinConditions, + $this->getVideoProperties() + ] + ]; + $result = $this->resourceEntryMediaGallery->loadDataFromTableByValueId( + InstallSchema::GALLERY_VALUE_VIDEO_TABLE, + $ids, + \Magento\Store\Model\Store::DEFAULT_STORE_ID, + [ + 'value_id' => 'value_id', + 'video_provider_default' => 'provider', + 'video_url_default' => 'url', + 'video_title_default' => 'title', + 'video_description_default' => 'description', + 'video_metadata_default' => 'metadata' + ], + $joinTable + ); + foreach ($result as &$item) { + $item = $this->substituteNullsWithDefaultValues($item); + } + + return $result; + } + + /** + * @param array $rowData + * @return mixed + */ + protected function substituteNullsWithDefaultValues(array $rowData) + { + foreach ($this->getVideoProperties(false) as $key) { + if (empty($rowData[$key]) && !empty($rowData[$key.'_default'])) { + $rowData[$key] = $rowData[$key.'_default']; + } + unset($rowData[$key.'_default']); + } + + return $rowData; + } + + /** + * @param bool $withDbMapping + * @return array + */ + protected function getVideoProperties($withDbMapping = true) + { + $properties = $this->videoPropertiesDbMapping; + unset($properties['value_id']); + unset($properties['store_id']); + + return $withDbMapping ? $properties : array_keys($properties); + } + + /** + * @param array $mediaCollection + * @param array $data + * @return array + */ + protected function addVideoDataToMediaEntries(array $mediaCollection, array $data) + { + $data = $this->createIndexedCollection($data); + foreach ($mediaCollection as &$mediaItem) { + if (array_key_exists($mediaItem['value_id'], $data)) { + $mediaItem = array_merge($mediaItem, $data[$mediaItem['value_id']]); + } + } + + return ['images' => $mediaCollection]; + } + + /** + * @param array $mediaCollection + * @param array $data + * @return array + */ + protected function addAdditionalStoreData(array $mediaCollection, array $data) + { + foreach ($mediaCollection as &$mediaItem) { + if (!empty($mediaItem['save_data_from'])) { + $additionalData = $this->createAdditionalStoreDataCollection($data, $mediaItem['save_data_from']); + if (!empty($additionalData)) { + $mediaItem[self::ADDITIONAL_STORE_DATA_KEY] = $additionalData; + } + } + } + + return ['images' => $mediaCollection]; + } + + /** + * @param array $storeData + * @param int $valueId + * @return array + */ + protected function createAdditionalStoreDataCollection(array $storeData, $valueId) + { + $result = []; + foreach ($storeData as $item) { + if ($item['value_id'] == $valueId) { + unset($item['value_id']); + $result[] = $item; + } + } + + return $result; + } + + /** + * @param array $mediaEntriesCollection + * @param string $indexKey + * @return array + */ + protected function createIndexedCollection(array $mediaEntriesCollection, $indexKey = 'value_id') + { + $indexedCollection = []; + foreach ($mediaEntriesCollection as $item) { + $id = $item[$indexKey]; + unset($item[$indexKey]); + $indexedCollection[$id] = $item; + } + + return $indexedCollection; + } +} diff --git a/app/code/Magento/ProductVideo/Model/Product/Attribute/Media/ExternalVideoEntryConverter.php b/app/code/Magento/ProductVideo/Model/Product/Attribute/Media/ExternalVideoEntryConverter.php new file mode 100644 index 0000000000000000000000000000000000000000..22ee48a92baa3bd06368ce37267bd99662d9166c --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/Product/Attribute/Media/ExternalVideoEntryConverter.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Model\Product\Attribute\Media; + +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface; +use Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory; + +/** + * Converter for External Video media gallery type + */ +class ExternalVideoEntryConverter extends ImageEntryConverter +{ + /** + * Media Entry type code + */ + const MEDIA_TYPE_CODE = 'external-video'; + + /** + * @var \Magento\Framework\Api\Data\VideoContentInterfaceFactory + */ + protected $videoEntryFactory; + + /** + * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory + */ + protected $mediaGalleryEntryExtensionFactory; + /** + * @param \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory + * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + * @param \Magento\Framework\Api\Data\VideoContentInterfaceFactory $videoEntryFactory + * @param ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory + */ + public function __construct( + \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory, + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + \Magento\Framework\Api\Data\VideoContentInterfaceFactory $videoEntryFactory, + ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory + ) { + parent::__construct($mediaGalleryEntryFactory, $dataObjectHelper); + $this->videoEntryFactory = $videoEntryFactory; + $this->mediaGalleryEntryExtensionFactory = $mediaGalleryEntryExtensionFactory; + } + + /** + * @return string + */ + public function getMediaEntryType() + { + return self::MEDIA_TYPE_CODE; + } + + /** + * @param Product $product + * @param array $rowData + * @return ProductAttributeMediaGalleryEntryInterface + */ + public function convertTo(Product $product, array $rowData) + { + $entry = parent::convertTo($product, $rowData); + $videoEntry = $this->videoEntryFactory->create(); + $this->dataObjectHelper->populateWithArray( + $videoEntry, + $rowData, + 'Magento\Framework\Api\Data\VideoContentInterface' + ); + $entryExtension = $this->mediaGalleryEntryExtensionFactory->create(); + $entryExtension->setVideoContent($videoEntry); + $entry->setExtensionAttributes($entryExtension); + return $entry; + } + + /** + * {@inheritdoc} + */ + public function convertFrom(ProductAttributeMediaGalleryEntryInterface $entry) + { + $dataFromPreviewImageEntry = parent::convertFrom($entry); + $videoContent = $entry->getExtensionAttributes()->getVideoContent(); + $entryArray = [ + 'video_provider' => $videoContent->getVideoProvider(), + 'video_url' => $videoContent->getVideoUrl(), + 'video_title' => $videoContent->getVideoTitle(), + 'video_description' => $videoContent->getVideoDescription(), + 'video_metadata' => $videoContent->getVideoMetadata(), + ]; + $entryArray = array_merge($dataFromPreviewImageEntry, $entryArray); + return $entryArray; + } +} diff --git a/app/code/Magento/ProductVideo/Model/Product/Attribute/Media/VideoEntry.php b/app/code/Magento/ProductVideo/Model/Product/Attribute/Media/VideoEntry.php new file mode 100644 index 0000000000000000000000000000000000000000..1228b140e0f5c5161f93158c8f72fe4daf47e765 --- /dev/null +++ b/app/code/Magento/ProductVideo/Model/Product/Attribute/Media/VideoEntry.php @@ -0,0 +1,163 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ProductVideo\Model\Product\Attribute\Media; + +use Magento\Framework\Model\AbstractExtensibleModel; +use Magento\Framework\Api\Data\VideoContentInterface; + +/** + * VideoEntry class + */ +class VideoEntry extends AbstractExtensibleModel implements VideoContentInterface +{ + /** + * Retrieve MIME type + * + * @return string + */ + public function getMediaType() + { + return $this->getData(self::TYPE); + } + + /** + * Set MIME type + * + * @param string $mimeType + * @return $this + */ + public function setMediaType($mimeType) + { + return $this->setData(self::TYPE, $mimeType); + } + + /** + * Get provider YouTube|Vimeo + * + * @return string + */ + public function getVideoProvider() + { + return $this->getData(self::PROVIDER); + } + + /** + * Set provider + * + * @param string $data + * @return $this + */ + public function setVideoProvider($data) + { + return $this->setData(self::PROVIDER, $data); + } + + /** + * Get video URL + * + * @return string + */ + public function getVideoUrl() + { + return $this->getData(self::URL); + } + + /** + * Set video URL + * + * @param string $data + * @return $this + */ + public function setVideoUrl($data) + { + return $this->setData(self::URL, $data); + } + + /** + * Get Title + * + * @return string + */ + public function getVideoTitle() + { + return $this->getData(self::TITLE); + } + + /** + * Set Title + * + * @param string $data + * @return $this + */ + public function setVideoTitle($data) + { + return $this->setData(self::TITLE, $data); + } + + /** + * Get video Description + * + * @return string + */ + public function getVideoDescription() + { + return $this->getData(self::DESCRIPTION); + } + + /** + * Set video Description + * + * @param string $data + * @return $this + */ + public function setVideoDescription($data) + { + return $this->setData(self::DESCRIPTION, $data); + } + + /** + * Get Metadata + * + * @return string + */ + public function getVideoMetadata() + { + return $this->getData(self::METADATA); + } + + /** + * Set Metadata + * + * @param string $data + * @return $this + */ + public function setVideoMetadata($data) + { + return $this->setData(self::METADATA, $data); + } + + /** + * Get extension attributes + * + * @return \Magento\Framework\Api\ExtensionAttributesInterface + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * Set extension attributes + * + * @param \Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes(\Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes) + { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/ProductVideo/README.md b/app/code/Magento/ProductVideo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e94e5aba8aba7a2f7d970bc28a5c2860dcf69722 --- /dev/null +++ b/app/code/Magento/ProductVideo/README.md @@ -0,0 +1 @@ +The Magento_ProductVideo module implements functionality related with linking video files from external resources to product. diff --git a/app/code/Magento/ProductVideo/Setup/InstallSchema.php b/app/code/Magento/ProductVideo/Setup/InstallSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..cfaa21939d4829d04dfdc96341de6d97de13cee3 --- /dev/null +++ b/app/code/Magento/ProductVideo/Setup/InstallSchema.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Setup; + +use Magento\Framework\Setup\InstallSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media; + +/** + * Class InstallSchema adds new table `eav_attribute_option_swatch` + */ +class InstallSchema implements InstallSchemaInterface +{ + /** + * Video Data Table name + */ + const GALLERY_VALUE_VIDEO_TABLE = 'catalog_product_entity_media_gallery_value_video'; + + /** + * {@inheritdoc} + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $contextInterface) + { + $setup->startSetup(); + + /** + * Create table 'catalog_product_entity_media_gallery_value_video' + */ + $table = $setup->getConnection() + ->newTable($setup->getTable(self::GALLERY_VALUE_VIDEO_TABLE)) + ->addColumn( + 'value_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false], + 'Media Entity ID' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'provider', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 32, + ['nullable' => true, 'default' => null], + 'Video provider ID' + ) + ->addColumn( + 'url', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + null, + ['nullable' => true, 'default' => null], + 'Video URL' + ) + ->addColumn( + 'title', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + ['nullable' => true, 'default' => null], + 'Title' + ) + ->addColumn( + 'description', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + null, + ['nullable' => true, 'default' => null], + 'Page Meta Description' + ) + ->addColumn( + 'metadata', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + null, + ['nullable' => true, 'default' => null], + 'Video meta data' + ) + ->addIndex( + $setup->getIdxName( + self::GALLERY_VALUE_VIDEO_TABLE, + ['value_id', 'store_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['value_id', 'store_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addForeignKey( + $setup->getFkName( + self::GALLERY_VALUE_VIDEO_TABLE, + 'value_id', + Media::GALLERY_TABLE, + 'value_id' + ), + 'value_id', + $setup->getTable(Media::GALLERY_TABLE), + 'value_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName( + self::GALLERY_VALUE_VIDEO_TABLE, + 'store_id', + $setup->getTable('store'), + 'store_id' + ), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment('Catalog Product Video Table'); + + $setup->getConnection()->createTable($table); + $setup->endSetup(); + } +} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php b/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php new file mode 100644 index 0000000000000000000000000000000000000000..55fe3af0f92a436b1757a96005a84a3992cd1e14 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Unit/Block/Adminhtml/Product/Edit/NewVideoTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ProductVideo\Test\Unit\Block\Adminhtml\Product\Edit; + +class NewVideoTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Backend\Block\Template\Context|\PHPUnit_Framework_MockObject_MockObject + */ + protected $contextMock; + + /* + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\UrlInterface + */ + protected $urlBuilder; + + /** + * @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mathRandom; + + /** + * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + */ + protected $registryMock; + + /** + * @var \Magento\Framework\Data\FormFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $formFactoryMock; + + /** + * @var \Magento\Framework\Json\EncoderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $jsonEncoderMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * |\Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo + */ + protected $block; + + public function setUp() + { + $this->contextMock = $this->getMock('\Magento\Backend\Block\Template\Context', [], [], '', false); + $this->mathRandom = $this->getMock('\Magento\Framework\Math\Random', [], [], '', false); + $this->urlBuilder = $this->getMock('\Magento\Framework\UrlInterface', [], [], '', false); + $this->contextMock->expects($this->any())->method('getMathRandom')->willReturn($this->mathRandom); + $this->contextMock->expects($this->any())->method('getUrlBuilder')->willReturn($this->urlBuilder); + $this->registryMock = $this->getMock('\Magento\Framework\Registry', [], [], '', false); + $this->formFactoryMock = $this->getMock('\Magento\Framework\Data\FormFactory', [], [], '', false); + $this->jsonEncoderMock = $this->getMock('\Magento\Framework\Json\EncoderInterface', [], [], '', false); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->block = $objectManager->getObject( + '\Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo', + [ + 'context' => $this->contextMock, + 'registry' => $this->registryMock, + 'formFactory' => $this->formFactoryMock, + 'jsonEncoder' => $this->jsonEncoderMock + ] + ); + } + + public function testGetHtmlId() + { + $this->mathRandom->expects($this->any())->method('getUniqueHash')->with('id_')->willReturn('id_' . rand()); + $this->block->getHtmlId(); + } + + public function testGetWidgetOptions() + { + $rand = rand(); + $this->mathRandom->expects($this->any())->method('getUniqueHash')->with('id_')->willReturn('id_' . $rand); + $saveVideoUrl = 'http://host/index.php/admin/catalog/product_gallery/upload/key/'; + $saveRemoteVideoUrl = 'http://host/index.php/admin/product_video/product_gallery/retrieveImage/'; + $this->urlBuilder->expects($this->exactly(2))->method('getUrl')->willReturnOnConsecutiveCalls( + $saveVideoUrl, + $saveRemoteVideoUrl + ); + $value = [ + 'saveVideoUrl' => $saveVideoUrl, + 'saveRemoteVideoUrl' => $saveRemoteVideoUrl, + 'htmlId' => 'id_' . $rand + ]; + $this->jsonEncoderMock->expects($this->once())->method('encode')->with( + $value + )->willReturn( + json_encode($value) + ); + $this->block->getWidgetOptions(); + } +} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/ObserverTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/ObserverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c1d60d275205cb6f77f5b7ca0567c43e04357b84 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/ObserverTest.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Test\Unit\Model; + + +class ObserverTest extends \PHPUnit_Framework_TestCase +{ + public function testChangeTemplate() + { + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Event\Observer $observer */ + $observer = $this->getMock('\Magento\Framework\Event\Observer', [], [], '', false); + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo $block + */ + $block = $this->getMock('\Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo', [], [], '', false); + $block->expects($this->once()) + ->method('setTemplate') + ->with('Magento_ProductVideo::helper/gallery.phtml') + ->willReturnSelf(); + $observer->expects($this->once())->method('__call')->with('getBlock')->willReturn($block); + + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\ProductVideo\Model\Observer $model */ + $model = $this->getMock('\Magento\ProductVideo\Model\Observer', null, [], '', false); + $model->changeTemplate($observer); + } +} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/ExternalVideoEntryProcessorTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/ExternalVideoEntryProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e531cdc248c4228592dfbb7cc10c8757498f27a7 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/ExternalVideoEntryProcessorTest.php @@ -0,0 +1,384 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Test\Unit\Model\Plugin; + +class ExternalVideoEntryProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Resource\ProductFactory */ + protected $productFactoryMock; + + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product */ + protected $productMock; + + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\MediaStorage\Helper\File\Storage\Database */ + protected $fileStorageDbMock; + + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Json\Helper\Data */ + protected $jsonHelperMock; + + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product\Media\Config */ + protected $mediaConfigMock; + + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Filesystem */ + protected $filesystemMock; + + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Filesystem\Directory\WriteInterface */ + protected $mediaDirectoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media + */ + protected $resourceEntryMediaGalleryMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Eav\Model\Entity\Attribute */ + protected $attributeMock; + + protected $mediaBackendModel; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * |\Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryProcessor + */ + protected $modelObject; + + public function setUp() + { + $this->productFactoryMock = + $this->getMock('\Magento\Catalog\Model\Resource\ProductFactory', ['create'], [], '', false); + + $this->productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + + $this->fileStorageDbMock = + $this->getMock('\Magento\MediaStorage\Helper\File\Storage\Database', [], [], '', false); + + $this->jsonHelperMock = $this->getMock('\Magento\Framework\Json\Helper\Data', [], [], '', false); + + $this->mediaConfigMock = $this->getMock('\Magento\Catalog\Model\Product\Media\Config', [], [], '', false); + + $this->filesystemMock = $this->getMock('\Magento\Framework\Filesystem', [], [], '', false); + $write = $this->getMock('\Magento\Framework\Filesystem\Directory\Write', [], [], '', false); + $this->filesystemMock->expects($this->any())->method('getDirectoryWrite')->with('media')->willReturn($write); + + $this->resourceEntryMediaGalleryMock = + $this->getMock('\Magento\Catalog\Model\Resource\Product\Attribute\Backend\Media', [], [], '', false); + + $this->attributeMock = $this->getMock('\Magento\Eav\Model\Entity\Attribute', [], [], '', false); + $this->attributeMock->expects($this->any())->method('getAttributeCode')->willReturn('media_gallery'); + + $this->mediaBackendModel = $this->getMock( + 'Magento\Catalog\Model\Product\Attribute\Backend\Media', + [], + [], + '', + false + ); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->modelObject = $objectManager->getObject( + '\Magento\ProductVideo\Model\Plugin\ExternalVideoEntryProcessor', + [ + 'productFactory' => $this->productFactoryMock, + 'fileStorageDb' => $this->fileStorageDbMock, + 'jsonHelper' => $this->jsonHelperMock, + 'mediaConfig' => $this->mediaConfigMock, + 'filesystem' => $this->filesystemMock, + 'resourceEntryMediaGallery' => $this->resourceEntryMediaGalleryMock + ] + ); + } + + public function testAfterAfterLoad() + { + $mediaData = [ + 'images' => [ + [ + 'value_id' => '4', + 'file' => '/i/n/index111111.jpg', + 'media_type' => 'external-video', + 'entity_id' => '1', + 'label' => '', + 'position' => '3', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '3', + 'disabled_default' => '0', + ], + [ + 'value_id' => '5', + 'file' => '/s/a/sample_3.jpg', + 'media_type' => 'external-video', + 'entity_id' => '1', + 'label' => '', + 'position' => '4', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '4', + 'disabled_default' => '0', + ], + [ + 'value_id' => '7', + 'file' => '/h/d/hd_image.jpg', + 'media_type' => 'image', + 'entity_id' => '1', + 'label' => '', + 'position' => '4', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '4', + 'disabled_default' => '0', + ] + ], + 'values' => [] + ]; + + $resourceEntryResult = [ + [ + 'value_id' => '4', + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title_default' => 'Some first title', + 'video_description_default' => 'Description first', + 'video_metadata_default' => 'meta one', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => 'Some first title', + 'video_description' => 'Description first', + 'video_metadata' => 'meta one', + ], + [ + 'value_id' => '5', + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title_default' => 'Some second title', + 'video_description_default' => 'Description second', + 'video_metadata_default' => 'meta two', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => '', + ] + ]; + + $this->productMock->expects($this->once())->method('getData')->with('media_gallery')->willReturn($mediaData); + $this->productMock->expects($this->once())->method('getStoreId')->willReturn(0); + + $this->resourceEntryMediaGalleryMock->expects($this->once())->method('loadDataFromTableByValueId')->willReturn( + $resourceEntryResult + ); + + $this->mediaBackendModel->expects($this->any())->method('getAttribute')->willReturn($this->attributeMock); + + $this->modelObject->afterAfterLoad($this->mediaBackendModel, $this->productMock); + } + + public function testAfterAfterSave() + { + $mediaData = [ + 'images' => [ + '72mljfhmasfilp9cuq' => [ + 'position' => '3', + 'media_type' => 'external-video', + 'file' => '/i/n/index111111.jpg', + 'value_id' => '4', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + ], + 'w596fi79hv1p6wj21u' => [ + 'position' => '4', + 'media_type' => 'image', + 'video_provider' => '', + 'file' => '/h/d/hd_image.jpg', + 'value_id' => '7', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_url' => '', + 'video_title' => '', + 'video_description' => '', + 'video_metadata' => '', + 'role' => '', + ], + 'tcodwd7e0dirifr64j' => [ + 'position' => '4', + 'media_type' => 'external-video', + 'file' => '/s/a/sample_3.jpg', + 'value_id' => '5', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + 'additional_store_data' => [ + 0 => + [ + 'store_id' => '0', + 'video_provider' => null, + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'New Title', + 'video_description' => 'New Description', + 'video_metadata' => 'New metadata', + ], + ] + ], + ], + ]; + + $this->productMock->expects($this->once())->method('getData')->with('media_gallery')->willReturn($mediaData); + $this->productMock->expects($this->once())->method('getStoreId')->willReturn(0); + $this->mediaBackendModel->expects($this->any())->method('getAttribute')->willReturn($this->attributeMock); + + $this->modelObject->afterAfterSave($this->mediaBackendModel, $this->productMock); + } + + public function testAfterAfterSaveEmpty() + { + $this->productMock->expects($this->once())->method('getData')->with('media_gallery')->willReturn([]); + $this->mediaBackendModel->expects($this->any())->method('getAttribute')->willReturn($this->attributeMock); + $this->modelObject->afterAfterSave($this->mediaBackendModel, $this->productMock); + } + + public function testAfterLoadNoVideo() + { + $mediaData = [ + 'images' => [ + [ + 'value_id' => '7', + 'file' => '/h/d/hd_image.jpg', + 'media_type' => 'image', + 'entity_id' => '1', + 'label' => '', + 'position' => '4', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '4', + 'disabled_default' => '0', + ] + ], + 'values' => [] + ]; + + $resourceEntryResult = []; + + $this->productMock->expects($this->once())->method('getData')->with('media_gallery')->willReturn($mediaData); + + $this->resourceEntryMediaGalleryMock->expects($this->once())->method('loadDataFromTableByValueId')->willReturn( + $resourceEntryResult + ); + $this->mediaBackendModel->expects($this->any())->method('getAttribute')->willReturn($this->attributeMock); + + $this->modelObject->afterAfterLoad($this->mediaBackendModel, $this->productMock); + } + + public function testBeforeSave() + { + $mediaData = [ + 'images' => [ + '72mljfhmasfilp9cuq' => [ + 'position' => '3', + 'media_type' => 'external-video', + 'file' => '/i/n/index111111.jpg', + 'value_id' => '4', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + ], + 'w596fi79hv1p6wj21u' => [ + 'position' => '4', + 'media_type' => 'external-video', + 'video_provider' => '', + 'file' => '/h/d/hd_image.jpg', + 'value_id' => '7', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_url' => '', + 'video_title' => '', + 'video_description' => '', + 'video_metadata' => '', + 'role' => '', + ], + 'tcodwd7e0dirifr64j' => [ + 'position' => '4', + 'media_type' => 'external-video', + 'video_provider' => '', + 'file' => '/h/d/hd_image.jpg', + 'value_id' => '', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_url' => '', + 'video_title' => '', + 'video_description' => '', + 'video_metadata' => '', + 'role' => '', + 'save_data_from' => '7', + 'new_file' => '/i/n/index_4.jpg', + ], + ], + ]; + + $this->productMock->expects($this->any())->method('getData')->with('media_gallery')->willReturn($mediaData); + $this->productMock->expects($this->any())->method('getStoreId')->willReturn(0); + + $resourceEntryResult = [ + [ + 'value_id' => '4', + 'store_id' => 1, + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title_default' => 'Some first title', + 'video_description_default' => 'Description first', + 'video_metadata_default' => 'meta one', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => 'Some first title', + 'video_description' => 'Description first', + 'video_metadata' => 'meta one', + ], + [ + 'value_id' => '7', + 'store_id' => 1, + 'video_provider_default' => 'youtube', + 'video_url_default' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title_default' => 'Some second title', + 'video_description_default' => 'Description second', + 'video_metadata_default' => 'meta two', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => '', + ] + ]; + + $this->resourceEntryMediaGalleryMock->expects($this->once())->method('loadDataFromTableByValueId')->willReturn( + $resourceEntryResult + ); + $this->mediaBackendModel->expects($this->any())->method('getAttribute')->willReturn($this->attributeMock); + + $this->modelObject->afterBeforeSave($this->mediaBackendModel, $this->productMock); + } +} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a04aafabb7d43b51aef0a7da683296b5481f1ba0 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/ExternalVideoEntryConverterTest.php @@ -0,0 +1,231 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ProductVideo\Test\Unit\Model\Product\Attribute\Media; + +use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; + +class ExternalVideoEntryConverterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory + */ + protected $mediaGalleryEntryFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface + */ + protected $mediaGalleryEntryMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Api\DataObjectHelper */ + protected $dataObjectHelperMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Api\Data\VideoContentInterfaceFactory */ + protected $videoEntryFactoryMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Api\Data\VideoContentInterface */ + protected $videoEntryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory + */ + protected $mediaGalleryEntryExtensionFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + * |\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory + */ + protected $mediaGalleryEntryExtensionMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * |\Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter + */ + protected $modelObject; + + public function setUp() + { + $this->mediaGalleryEntryFactoryMock = + $this->getMock( + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory', + ['create'], + [], + '', + false + ); + + $this->mediaGalleryEntryMock = + $this->getMock( + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface', + [ + 'getId', + 'setId', + 'getMediaType', + 'setMediaType', + 'getLabel', + 'setLabel', + 'getPosition', + 'setPosition', + 'isDisabled', + 'setDisabled', + 'getTypes', + 'setTypes', + 'getFile', + 'setFile', + 'getContent', + 'setContent', + 'getExtensionAttributes', + 'setExtensionAttributes' + ], + [], + '', + false + ); + + $this->mediaGalleryEntryFactoryMock->expects($this->any())->method('create')->willReturn( + $this->mediaGalleryEntryMock + ); + + $this->dataObjectHelperMock = $this->getMock('\Magento\Framework\Api\DataObjectHelper', [], [], '', false); + + $this->videoEntryFactoryMock = + $this->getMock('\Magento\Framework\Api\Data\VideoContentInterfaceFactory', ['create'], [], '', false); + + $this->videoEntryMock = $this->getMock('\Magento\Framework\Api\Data\VideoContentInterface', [], [], '', false); + + $this->videoEntryFactoryMock->expects($this->any())->method('create')->willReturn($this->videoEntryMock); + + $this->mediaGalleryEntryExtensionFactoryMock = + $this->getMock( + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory', + ['create'], + [], + '', + false + ); + + $this->mediaGalleryEntryExtensionMock = $this->getMock( + '\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtension', + ['setVideoContent', 'getVideoContent', 'getVideoProvider'], + [], + '', + false + ); + + $this->mediaGalleryEntryExtensionMock->expects($this->any())->method('setVideoContent')->willReturn(null); + $this->mediaGalleryEntryExtensionFactoryMock->expects($this->any())->method('create')->willReturn( + $this->mediaGalleryEntryExtensionMock + ); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->modelObject = $objectManager->getObject( + '\Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter', + [ + 'mediaGalleryEntryFactory' => $this->mediaGalleryEntryFactoryMock, + 'dataObjectHelper' => $this->dataObjectHelperMock, + 'videoEntryFactory' => $this->videoEntryFactoryMock, + 'mediaGalleryEntryExtensionFactory' => $this->mediaGalleryEntryExtensionFactoryMock + ] + ); + } + + public function testGetMediaEntryType() + { + $this->assertEquals($this->modelObject->getMediaEntryType(), 'external-video'); + } + + public function testConvertTo() + { + /** @var $product \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product */ + $product = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + + $rowData = [ + 'value_id' => '4', + 'file' => '/i/n/index111111.jpg', + 'media_type' => ExternalVideoEntryConverter::MEDIA_TYPE_CODE, + 'entity_id' => '1', + 'label' => '', + 'position' => '3', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '3', + 'disabled_default' => '0', + 'video_provider' => null, + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => '111', + 'video_description' => null, + 'video_metadata' => null, + ]; + + $productImages = [ + 'image' => '/s/a/sample_3.jpg', + 'small_image' => '/s/a/sample-1_1.jpg', + 'thumbnail' => '/s/a/sample-1_1.jpg', + 'swatch_image' => '/s/a/sample_3.jpg', + ]; + + $product->expects($this->once())->method('getMediaAttributeValues')->willReturn($productImages); + + $this->mediaGalleryEntryMock->expects($this->once())->method('setExtensionAttributes')->will( + $this->returnSelf() + ); + + $this->modelObject->convertTo($product, $rowData); + } + + public function testConvertFrom() + { + $this->mediaGalleryEntryMock->expects($this->once())->method('getId')->willReturn('4'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getFile')->willReturn('/i/n/index111111.jpg'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getLabel')->willReturn('Some Label'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getPosition')->willReturn('3'); + $this->mediaGalleryEntryMock->expects($this->once())->method('isDisabled')->willReturn('0'); + $this->mediaGalleryEntryMock->expects($this->once())->method('getTypes')->willReturn([]); + $this->mediaGalleryEntryMock->expects($this->once())->method('getContent')->willReturn(null); + + $this->mediaGalleryEntryMock->expects($this->once())->method('getExtensionAttributes')->willReturn( + $this->mediaGalleryEntryExtensionMock + ); + + $videoContentMock = + $this->getMock('Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntry', [], [], '', false); + + $videoContentMock->expects($this->once())->method('getVideoProvider')->willReturn('youtube'); + $videoContentMock->expects($this->once())->method('getVideoUrl')->willReturn( + 'https://www.youtube.com/watch?v=abcdefghij' + ); + $videoContentMock->expects($this->once())->method('getVideoTitle')->willReturn('Some video title'); + $videoContentMock->expects($this->once())->method('getVideoDescription')->willReturn('Some video description'); + $videoContentMock->expects($this->once())->method('getVideoMetadata')->willReturn('Meta data'); + + $this->mediaGalleryEntryExtensionMock->expects($this->once())->method('getVideoContent')->willReturn( + $videoContentMock + ); + + $expectedResult = [ + 'value_id' => '4', + 'file' => '/i/n/index111111.jpg', + 'label' => 'Some Label', + 'position' => '3', + 'disabled' => '0', + 'types' => [], + 'media_type' => null, + 'content' => null, + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=abcdefghij', + 'video_title' => 'Some video title', + 'video_description' => 'Some video description', + 'video_metadata' => 'Meta data', + ]; + + $result = $this->modelObject->convertFrom($this->mediaGalleryEntryMock); + $this->assertEquals($expectedResult, $result); + } +} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/VideoEntryTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/VideoEntryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b92f1797913061467ab52764a587489ece354c01 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/Product/Attribute/Media/VideoEntryTest.php @@ -0,0 +1,102 @@ +<?php +/** + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ProductVideo\Test\Unit\Model\Product\Attribute\Media; + +/** + * VideoEntry test + */ +class VideoEntryTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntry|\PHPUnit_Framework_MockObject_MockObject */ + protected $modelObject; + + public function setUp() + { + $this->modelObject = + $this->getMock( + '\Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntry', + ['getData', 'setData'], + [], + '', + false + ); + } + + public function testGetMediaType() + { + $this->modelObject->expects($this->once())->method('getData')->willReturn('image'); + $this->modelObject->getMediaType(); + } + + public function testSetMediaType() + { + $this->modelObject->expects($this->once())->method('setData')->willReturn($this->modelObject); + $this->modelObject->setMediaType('image'); + } + + public function testGetVideoProvider() + { + $this->modelObject->expects($this->once())->method('getData')->willReturn('youtube'); + $this->modelObject->getMediaType(); + } + + public function testSetVideoProvider() + { + $this->modelObject->expects($this->once())->method('setData')->willReturn($this->modelObject); + $this->modelObject->setMediaType('vimeo'); + } + + public function testGetVideoUrl() + { + $this->modelObject->expects($this->once())->method('getData')->willReturn( + 'https://www.youtube.com/watch?v=abcdefghij' + ); + $this->modelObject->getMediaType(); + } + + public function testSetVideoUrl() + { + $this->modelObject->expects($this->once())->method('setData')->willReturn($this->modelObject); + $this->modelObject->setMediaType('https://www.youtube.com/watch?v=abcdefghij'); + } + + public function testGetVideoTitle() + { + $this->modelObject->expects($this->once())->method('getData')->willReturn('Title'); + $this->modelObject->getMediaType(); + } + + public function testSetVideoTitle() + { + $this->modelObject->expects($this->once())->method('setData')->willReturn($this->modelObject); + $this->modelObject->setMediaType('Title'); + } + + public function testGetVideoDescription() + { + $this->modelObject->expects($this->once())->method('getData')->willReturn('Description'); + $this->modelObject->getMediaType(); + } + + public function testSetVideoDescription() + { + $this->modelObject->expects($this->once())->method('setData')->willReturn($this->modelObject); + $this->modelObject->setMediaType('Description'); + } + + public function testGetVideoMetadata() + { + $this->modelObject->expects($this->once())->method('getData')->willReturn('Meta data'); + $this->modelObject->getMediaType(); + } + + public function testSetVideoMetadata() + { + $this->modelObject->expects($this->once())->method('setData')->willReturn($this->modelObject); + $this->modelObject->setMediaType('Meta data'); + } +} diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..d58f65eb9ad718000a2806298bf7644a022ad01e --- /dev/null +++ b/app/code/Magento/ProductVideo/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-product-video", + "description": "Add Video to Products", + "require": { + "php": "~5.5.0|~5.6.0", + "magento/module-catalog": "1.0.0-beta", + "magento/module-backend": "1.0.0-beta", + "magento/module-customer": "1.0.0-beta", + "magento/module-eav": "1.0.0-beta", + "magento/module-store": "1.0.0-beta", + "magento/framework": "1.0.0-beta", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-module", + "version": "1.0.0-beta", + "license": [ + "proprietary" + ], + "extra": { + "map": [ + [ + "*", + "Magento/ProductVideo" + ] + ] + } +} diff --git a/app/code/Magento/ProductVideo/etc/adminhtml/events.xml b/app/code/Magento/ProductVideo/etc/adminhtml/events.xml new file mode 100644 index 0000000000000000000000000000000000000000..89f8e14360b2d6b259dceff6033ec444ddc12c68 --- /dev/null +++ b/app/code/Magento/ProductVideo/etc/adminhtml/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/Event/etc/events.xsd"> + <event name="catalog_product_gallery_prepare_layout"> + <observer name="change_template" instance="Magento\ProductVideo\Model\Observer" method="changeTemplate" /> + </event> +</config> diff --git a/app/code/Magento/ProductVideo/etc/di.xml b/app/code/Magento/ProductVideo/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..64a91ddc217fc0a8e0193f04b791325f6242a696 --- /dev/null +++ b/app/code/Magento/ProductVideo/etc/di.xml @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <preference for="Magento\Framework\Api\Data\VideoContentInterface" type="Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntry" /> + <type name="Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool"> + <arguments> + <argument name="mediaGalleryEntryConvertersCollection" xsi:type="array"> + <item name="external-video" xsi:type="object">Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter</item> + </argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\Product\Attribute\Backend\Media"> + <plugin name="external_video_media_entry_processor" type="Magento\ProductVideo\Model\Plugin\ExternalVideoEntryProcessor" /> + </type> + <type name="Magento\Catalog\Block\Adminhtml\Product\Helper\Form\BaseImage"> + <plugin name="base_image_element_render" type="Magento\ProductVideo\Model\Plugin\BaseImage" /> + </type> +</config> diff --git a/app/code/Magento/ProductVideo/etc/extension_attributes.xml b/app/code/Magento/ProductVideo/etc/extension_attributes.xml new file mode 100644 index 0000000000000000000000000000000000000000..53c5e40db57372fe468aa8211427e722c380b70d --- /dev/null +++ b/app/code/Magento/ProductVideo/etc/extension_attributes.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/extension_attributes.xsd"> + <extension_attributes for="Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface"> + <attribute code="video_content" type="Magento\Framework\Api\Data\VideoContentInterface" /> + </extension_attributes> +</config> \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/etc/module.xml b/app/code/Magento/ProductVideo/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..7c2c42fe48cd9827353092af5401b71b30740d70 --- /dev/null +++ b/app/code/Magento/ProductVideo/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** +* Copyright © 2015 Magento. All rights reserved. +* See COPYING.txt for license details. +*/ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> + <module name="Magento_ProductVideo" setup_version="2.0.0.1"> + <sequence> + <module name="Magento_Catalog"/> + <module name="Magento_Backend"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml new file mode 100755 index 0000000000000000000000000000000000000000..a214672c79e7c99d17a76ca9b5b27054b540f68a --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd"> + <head> + <css src="Magento_ProductVideo::css/productvideo.css"/> + </head> + <body> + <referenceContainer name="content"> + <block class="Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo" name="new-video" template="Magento_ProductVideo::product/edit/slideout/form.phtml"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/requirejs-config.js b/app/code/Magento/ProductVideo/view/adminhtml/requirejs-config.js new file mode 100644 index 0000000000000000000000000000000000000000..ec985e6e1c183074737045298520d04b36a6d4b2 --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/requirejs-config.js @@ -0,0 +1,13 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*eslint no-unused-vars: 0*/ +var config = { + map: { + '*': { + newVideoDialog: 'Magento_ProductVideo/js/new-video-dialog', + openVideoModal: 'Magento_ProductVideo/js/video-modal' + } + } +}; diff --git a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml new file mode 100755 index 0000000000000000000000000000000000000000..d1c235e08192b895d850786275cb48d67dd5c740 --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml @@ -0,0 +1,205 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content */ +$elementName = $block->getElement()->getName() . '[images]'; +?> +<div id="<?php /* @escapeNotVerified */ echo $block->getHtmlId() ?>" + class='gallery' + data-mage-init='{"productGallery":{"template":"#<?php + /* @escapeNotVerified */ echo $block->getHtmlId() ?>-template"}}' + data-images="<?php /* @escapeNotVerified */ echo $block->escapeHtml($block->getImagesJson()) ?>" + data-types="<?php /* @escapeNotVerified */ echo $block->escapeHtml( + $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getImageTypes()) + ) ?>" + > + <?php + if (!$block->getElement()->getReadonly()): + ?> + <div class="image image-placeholder"> + <?php /* @escapeNotVerified */ echo $block->getUploaderHtml(); + ?> + <img class="spacer" + src="<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/spacer.gif') ?>"/> + + <p class="image-placeholder-text"> + <?php /* @escapeNotVerified */ echo __('Click here or drag and drop to add images.'); ?> + </p> + </div> + <?php /* @escapeNotVerified */ echo $block->getChildHtml('additional_buttons'); ?> + <div class="image video-placeholder"> + <button id="add_video_button" title="<?php /* @escapeNotVerified */ echo __('New Video'); ?>" type="button" + class="action-default scalable" data-mage-init='{"openVideoModal" : {}}' + data-ui-id="widget-button-1"> + <img class="spacer" + src="<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/spacer.gif') ?>"/> + + <p class="image-placeholder-text"> + <?php /* @escapeNotVerified */ echo __('Click here to add videos.'); ?> + </p> + </button> + </div> + <?php + endif; + ?> + <?php + foreach ($block->getImageTypes() as $typeData): ?> + <input name="<?php /* @escapeNotVerified */ echo $block->escapeHtml($typeData['name']) ?>" + class="image-<?php /* @escapeNotVerified */ echo $block->escapeHtml($typeData['code']) ?>" + type="hidden" + value="<?php /* @escapeNotVerified */ echo $block->escapeHtml($typeData['value']) ?>"/> + <?php + endforeach; ?> + + <script id="<?php /* @escapeNotVerified */ echo $block->getHtmlId() ?>-template" type="text/x-magento-template"> + <div class="image item <% if (data.disabled == 1) { %>hidden-for-front<% } %>" + data-role="image"> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][position]" + value="<%- data.position %>" class="position"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][media_type]" + value="<%- data.media_type %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][video_provider]" + value="<%- data.video_provider %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][file]" + value="<%- data.file %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][value_id]" + value="<%- data.value_id %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][label]" + value="<%- data.label %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][disabled]" + value="<%- data.disabled %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][removed]" + value="" + class="is-removed"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][video_url]" + value="<%- data.video_url %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][video_title]" + value="<%- data.video_title %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][video_description]" + value="<%- data.video_description %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][video_metadata]" + value="<%- data.video_metadata %>"/> + <input type="hidden" + name="<?php /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][role]" + value="<%- data.video_description %>"/> + <ul class="type-labels" style="display: none"> + <?php + foreach ($block->getImageTypes() as $typeData): ?> + <li class="type-<?php /* @escapeNotVerified */ echo $block->escapeHtml( + $typeData['code'] + ) ?>"><?php /* @escapeNotVerified */ echo $block->escapeHtml($typeData['label']) ?></li> + <?php + endforeach; ?> + </ul> + <img class="spacer" + src="<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/spacer.gif') ?>"/> + <img class="product-image" src="<%- data.url %>" alt="<%- data.label %>"/> + + <div class="actions"> + <button type="button" class="action-delete" data-role="delete-button" + title="<?php /* @escapeNotVerified */ echo __('Delete image') ?>"> + <span> <?php /* @escapeNotVerified */ echo __('Delete image') ?></span> + </button> + <button type="button" class="action-make-base" data-role="make-base-button" + title="<?php /* @escapeNotVerified */ echo __('Make Base') ?>"> + <span><?php /* @escapeNotVerified */ echo __('Make Base') ?></span> + </button> + <div class="draggable-handle"></div> + </div> + <div class="image-label"></div> + <div class="image-fade"><span><?php /* @escapeNotVerified */ echo __('Hidden') ?></span></div> + </div> + </script> + <script class="dialog-template" type="text/x-magento-template" data-title="Image Options"> + <div class="image-panel" data-role="dialog"> + <div class="image-panel-preview"> + <img src="<%- data.url %>" alt="<%- data.label %>"/> + </div> + <div class="image-panel-controls"> + <strong class="image-name"><%- data.label %></strong> + + <fieldset class="fieldset fieldset-image-panel"> + <div class="field field-image-description"> + <label class="label" for="image-description"> + <span><?php /* @escapeNotVerified */ echo __('Alt Text') ?></span> + </label> + + <div class="control"> + <textarea id="image-description" + rows="3" + name="<?php + /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][label]"> + <%- data.label %> + </textarea> + </div> + </div> + + <div class="field field-image-role"> + <label class="label"> + <span><?php /* @escapeNotVerified */ echo __('Role') ?></span> + </label> + + <div class="control"> + <ul class="multiselect-alt"> + <?php + foreach ($block->getMediaAttributes() as $attribute): ?> + <li class="item"> + <label> + <input class="image-type" + data-role="type-selector" + type="checkbox" + value="<?php + /* @escapeNotVerified */ echo $block->escapeHtml( + $attribute->getAttributeCode() + ) ?>" + /> + <?php /* @escapeNotVerified */ echo $block->escapeHtml( + $attribute->getFrontendLabel() + ) ?> + </label> + </li> + <?php + endforeach; ?> + </ul> + </div> + </div> + + <div class="field field-image-hide"> + <label class="label"> + <input type="checkbox" + data-role="visibility-trigger" + value="1" + name="<?php + /* @escapeNotVerified */ echo $elementName ?>[<%- data.file_id %>][disabled]" + <% if (data.disabled == 1) { %>checked="checked"<% } %> /> + <span><?php /* @escapeNotVerified */ echo __('Hide from Product Page') ?></span> + </label> + </div> + </fieldset> + </div> + + <button type="button" + class="action-close" + data-role="close-panel" + title="<?php /* @escapeNotVerified */ echo __('Close panel'); ?>"> + <span><?php /* @escapeNotVerified */ echo __('Close panel'); ?></span> + </button> + <div class="image-pointer"></div> + </div> + </script> +</div> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/templates/product/edit/base_image.phtml b/app/code/Magento/ProductVideo/view/adminhtml/templates/product/edit/base_image.phtml new file mode 100644 index 0000000000000000000000000000000000000000..6fc06b824b269a1a9f7844c09d4006c4cd035da1 --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/templates/product/edit/base_image.phtml @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<div id="<?php /* @escapeNotVerified */ echo $htmlId;?>-container" class="images" + data-mage-init='{"baseImage":{}}' + data-max-file-size="<?php /* @escapeNotVerified */ echo $fileMaxSize;?>" + > + <div class="image image-placeholder"> + <input type="file" + name="image" + data-url="<?php /* @escapeNotVerified */ echo $uploadUrl;?>" + multiple="multiple" /> + <img class="spacer" src="<?php /* @escapeNotVerified */ echo $spacerImage;?>"/> + <p class="image-placeholder-text"><?php /* @escapeNotVerified */ echo $imagePlaceholderText;?></p> + </div> + <div class="image video-placeholder"> + <button + id="add_video_button" + title="<?php /* @escapeNotVerified */ echo $addVideoTitle;?>" + type="button" + class="action-default scalable" + onclick="jQuery('#new-video').modal('openModal'); jQuery('#new_video_form')[0].reset();" + data-ui-id="widget-button-1"> + <img class="spacer" src="<?php /* @escapeNotVerified */ echo $spacerImage;?>"/> + <p class="image-placeholder-text"><?php /* @escapeNotVerified */ echo $videoPlaceholderText;?></p> + </button> + </div> + <script id="<?php /* @escapeNotVerified */ echo $htmlId;?>-template" + data-template="image" + type="text/x-magento-template"> + <div class="image"> + <img class="spacer" src="<?php /* @escapeNotVerified */ echo $spacerImage;?>"/> + <img + class="product-image" + src="<%- data.url %>" + data-position="<%- data.position %>" + alt="<%- data.label %>" /> + <div class="actions"> + <button + type="button" + class="action-delete" + data-role="delete-button" + title="<?php /* @escapeNotVerified */ echo $deleteImageText;?>"> + <span><?php /* @escapeNotVerified */ echo $deleteImageText;?></span> + </button> + <button + type="button" + class="action-make-base" + data-role="make-base-button" + title="<?php /* @escapeNotVerified */ echo $makeBaseText;?>"> + <span><?php /* @escapeNotVerified */ echo $makeBaseText;?></span> + </button> + <div class="draggable-handle"></div> + </div> + <div class="image-label"></div> + <div class="image-fade"><span><?php /* @escapeNotVerified */ echo $hiddenText;?></span></div> + </div> + </script> +</div> +<span class="action-manage-images" data-activate-tab="image-management"> + <span><?php /* @escapeNotVerified */ echo $imageManagementText;?></span> +</span> +<script> + require([ + 'jquery' + ],function($){ + + 'use strict'; + + $('[data-activate-tab=image-management]') + .on('click.toggleImageManagementTab', function() { + $('#product_info_tabs_image-management').trigger('click'); + }); + }); +</script> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/templates/product/edit/slideout/form.phtml b/app/code/Magento/ProductVideo/view/adminhtml/templates/product/edit/slideout/form.phtml new file mode 100644 index 0000000000000000000000000000000000000000..cc95c9c5791d678be5ccc80192acb5533d5569be --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/templates/product/edit/slideout/form.phtml @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/* @var Magento\ProductVideo\Block\Adminhtml\Product\Edit\NewVideo $block */ +?> +<div id="<?php /* @escapeNotVerified */ echo $block->getNameInLayout();?>" style="display:none"> + <?php /* @escapeNotVerified */ echo $block->getFormHtml();?> + <script> + require(["jquery","mage/mage"],function($) { + $(function(){ + $('#new-video').mage( + 'newVideoDialog', + <?php /* @escapeNotVerified */ echo $block->getWidgetOptions();?> + ); + }); + }); + </script> +</div> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/css/productvideo.css b/app/code/Magento/ProductVideo/view/adminhtml/web/css/productvideo.css new file mode 100755 index 0000000000000000000000000000000000000000..7861103573d2d7e4e4d5f3af02d9f188ed9d0bb4 --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/css/productvideo.css @@ -0,0 +1,117 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +.image.video-placeholder { + position: relative; + display: inline-block; + text-decoration: none; +} + +.image.video-placeholder { + background-color: #fff; + font-size: 4rem; + line-height: inherit; + color: #ccc; + overflow: hidden; + speak: none; + font-weight: normal; + -webkit-font-smoothing: antialiased; + display: inline-block; + vertical-align: middle; + text-align: center; + position: relative; +} +.image.video-placeholder:before { + background: url(../images/gallery-sprite.png) no-repeat left bottom; + content: ''; + position: absolute; + height: 49px; + width: 49px; + left: 50%; + top: 18px; + margin-left: -24px; + opacity: 0.7; +} +#add_video_button { + background: rgba(0,0,0,0); + width: 100%; + height: 100%; + border: 0; + z-index: 5; + position: relative; +} +.video-placeholder .image-placeholder-text { + font-weight: 400; +} +.admin__field.field-video_base_image .admin__field-control, +.admin__field.field-video_small_image .admin__field-control, +.admin__field.field-video_thumb_image .admin__field-control, +.admin__field.field-video_swatch_image .admin__field-control, +.admin__field.field-new_video_disabled .admin__field-control { + width: 82px; + margin-left: calc( (100%) * .33333333 - 30px ); +} +.admin__field.field-video_base_image .admin__field-control input, +.admin__field.field-video_small_image .admin__field-control input, +.admin__field.field-video_thumb_image .admin__field-control input, +.admin__field.field-video_swatch_image .admin__field-control input, +.admin__field.field-new_video_disabled .admin__field-control input { + float: right; +} +.admin__field.field-video_base_image .admin__field-label, +.admin__field.field-video_small_image .admin__field-label, +.admin__field.field-video_thumb_image .admin__field-label, +.admin__field.field-video_swatch_image .admin__field-label, +.admin__field.field-new_video_disabled .admin__field-label { + width: 200px; + position: absolute; + margin-left: calc( (100%) * .33333333 - 30px + 90px ); +} +.admin__field.field-video_base_image .admin__field-label span, +.admin__field.field-video_small_image .admin__field-label span, +.admin__field.field-video_thumb_image .admin__field-label span, +.admin__field.field-video_swatch_image .admin__field-label span, +.admin__field.field-new_video_disabled .admin__field-label span { + float: left; +} +.admin__field.field-video_base_image, +.admin__field.field-video_small_image, +.admin__field.field-video_thumb_image, +.admin__field.field-video_swatch_image { + margin-bottom: 20px !important; +} +.admin__field.field-new_video_disabled { + margin-top: 32px !important; +} +.admin__field.field-video_base_image .admin__field-control { + position: relative; +} +.admin__field.field-video_base_image .admin__field-control:after { + content: 'Role'; + position: absolute; + color: #000; + width: 34px; + height: 20px; + left: 1px; + top: -2px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.4rem; + font-weight: 600; +} + +.preview_hidden_image_input_button { + display: none; +} +.video-item { + position: relative; +} +.video-item:after { + content: ''; + position: absolute; + bottom: 0; + right: 9px; + background: url(../images/gallery-sprite.png) bottom left; + width: 49px; + height: 40px; +} \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/images/gallery-sprite.png b/app/code/Magento/ProductVideo/view/adminhtml/web/images/gallery-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a8a9a2a76e7c7360b892dab703c0e15270a7a8 Binary files /dev/null and b/app/code/Magento/ProductVideo/view/adminhtml/web/images/gallery-sprite.png differ diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js new file mode 100644 index 0000000000000000000000000000000000000000..bbddacac644ae500517a70cddfdb75316230d0e5 --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js @@ -0,0 +1,685 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/*jshint browser:true $:true*/ +define([ + 'jquery', + 'Magento_Ui/js/modal/alert', + 'jquery/ui', + 'Magento_Ui/js/modal/modal', + 'mage/translate', + 'mage/backend/tree-suggest', + 'mage/backend/validation' +], function ($, alert) { + 'use strict'; + + /** + */ + $.widget('mage.newVideoDialog', { + + _previewImage: null, + + clickedElement: '', + + _images: {}, + + _imageTypes: [ + 'image/jpeg', + 'image/pjpeg', + 'image/jpeg', + 'image/pjpeg', + 'image/png', + 'image/gif' + ], + + /** + * Bind events + * @private + */ + _bind: function () { + var events = { + 'setImage': '_onSetImage' + }; + + this._on(events); + }, + + /** + * Remove ".tmp" + * @param {String} name + * @returns {*} + * @private + */ + __prepareFilename: function (name) { + var tmppost = '.tmp'; + + if (!name) { + return null; + } + + if (name.endsWith(tmppost)) { + name = name.slice(0, name.length - tmppost.length); + } + + return name; + }, + + /** + * Set image data + * @param {String} file + * @param {Object} imageData + * @private + */ + _setImage: function (file, imageData) { + file = this.__prepareFilename(file); + this._images[file] = imageData; + $('#media_gallery_content').trigger('addItem', imageData); + this.element.trigger('setImage', imageData); + this._addVideoClass(imageData.url); + }, + + /** + * Get image data + * + * @param {String} file + * @returns {*} + * @private + */ + _getImage: function (file) { + file = this.__prepareFilename(file); + + return this._images[file]; + }, + + /** + * Replace image + * @param {String} oldFile + * @param {String} newFile + * @param {Object} imageData + * @private + */ + _replaceImage: function (oldFile, newFile, imageData) { + var tmpNewFile = newFile, + newImageId, + fc, + suff, + searchsuff, + key, + oldValIdElem; + + oldFile = this.__prepareFilename(oldFile); + newFile = this.__prepareFilename(newFile); + + if (newFile === oldFile) { + this._images[newFile] = imageData; + this.saveImageRoles(imageData); + + return null; + } + + this._removeImage(oldFile); + this._setImage(newFile, imageData); + + if (oldFile && imageData.oldFile) { + newImageId = this.findElementId(tmpNewFile), + fc = $('#item_id').val(), + suff = 'product[media_gallery][images]' + fc, + searchsuff = 'input[name="' + suff + '[value_id]"]', + key = $(searchsuff).val(); + + if (!key) { + return null; + } + + oldValIdElem = document.createElement('input'); + $('form[data-form="edit-product"]').append(oldValIdElem); + $(oldValIdElem).attr({ + type: 'hidden', + name: 'product[media_gallery][images][' + newImageId + '][save_data_from]' + }).val(key); + } + }, + + /** + * Remove image data + * @param {String} file + * @private + */ + _removeImage: function (file) { + var imageData = this._getImage(file); + + if (!imageData) { + return null; + } + + $('#media_gallery_content').trigger('removeItem', imageData); + this.element.trigger('removeImage', imageData); + delete this._images[file]; + }, + + /** + * Fired when image setted + * @param {Event} event + * @param {Object} imageData + * @private + */ + _onSetImage: function (event, imageData) { + this.saveImageRoles(imageData); + }, + + /** + * Upload local image + * @param {String} file + * @param {String} oldFile + * @param {Function} callback + * @private + */ + _uploadImage: function (file, oldFile, callback) { + var self = this, + url = this.options.saveVideoUrl, + uploadData = { + files: file, + url: url + }; + + this._uploadFile('send', uploadData, function (result) { + var data = JSON.parse(result); + + if (data && data.errorcode) { + alert({ + content: data.error + }); + + return null; + } + + $.each($('#new_video_form').serializeArray(), function (i, field) { + data[field.name] = field.value; + }); + data.disabled = $('#new_video_disabled').prop('checked') ? 1 : 0; + data['media_type'] = 'external-video'; + data['old_file'] = oldFile; + oldFile ? + self._replaceImage(oldFile, data.file, data) : + self._setImage(data.file, data); + + callback.call(0, data); + }); + }, + + /** + * Upload local file + * @private + */ + _uploadFile: function (method, data, callback) { + var fu = $('#new_video_screenshot'), + tmpInput = document.createElement('input'), + fileUploader = null; + + $(tmpInput).attr({ + name: fu.attr('name'), + value: fu.val(), + type: 'file', + 'data-ui-ud': fu.attr('data-ui-ud') + }).css('display', 'none'); + fu.parent().append(tmpInput); + fileUploader = $(tmpInput).fileupload(); + fileUploader.fileupload(method, data).success(function (result, textStatus, jqXHR) { + tmpInput.remove(); + callback.call(null, result, textStatus, jqXHR); + }); + }, + + /** + * Add video class (gallery items) + * @param {String} url + * @private + */ + _addVideoClass: function (url) { + var classVideo = 'video-item'; + + $('img[src="' + url + '"]').addClass(classVideo); + }, + + /** + * Fired when widget initialized + * @private + */ + _create: function () { + var imgs = $('#media_gallery_content').data('images') || [], + widget, + uploader, + tmp, + i; + + for (i = 0; i < imgs.length; i++) { + tmp = imgs[i]; + tmp.subclass = 'video-item'; + this._images[tmp.file] = tmp; + this._addVideoClass(tmp.url); + } + + this._bind(); + widget = this; + uploader = $('#new_video_screenshot'); + uploader.on('change', this._onImageInputChange.bind(this)); + this.toggleButtons(); + uploader.attr('accept', this._imageTypes.join(',')); + this.element.modal({ + type: 'slide', + modalClass: 'mage-new-video-dialog form-inline', + title: $.mage.__('Create Video'), + buttons: [{ + text: $.mage.__('Save'), + class: 'action-primary video-create-button', + + /** + * Fired when click on Save button + */ + click: function () { + var nvs = $('#new_video_screenshot'), + file = nvs.get(0), + newVideoForm, + reqClass = 'required-entry _required'; + + if (file && file.files && file.files.length) { + file = file.files[0]; + } else { + file = null; + } + + if (!file) { + nvs.addClass(reqClass); + } + + newVideoForm = $('#new_video_form'); + newVideoForm.mage('validation', { + + /** + * Set invalid message + * @param {jQuery} error + * @param {jQuery} element + */ + errorPlacement: function (error, element) { + error.insertAfter(element); + } + }).on('highlight.validate', function () { + $(this).validation('option'); + }); + newVideoForm.validation(); + + if (!newVideoForm.valid()) { + return null; + } + + widget._uploadImage(file, null, function () { + //uploader.replaceWith(data.file); + widget._onClose(); + }); + nvs.removeClass(reqClass); + } + }, + { + text: $.mage.__('Save'), + class: 'action-primary video-edit', + + /** + * Fired when video edit + */ + click: function () { + var newVideoForm = $('#new_video_form'), + inputFile, + flagChecked, + imageData, + fileName, + itemVal, + mediaFields, + + /** + * Callback for event "modalClose" + */ + callback = function () { + widget._onClose(); + }; + + newVideoForm.mage('validation', { + + /** + * Set invalid message + * @param {jQuery} error + * @param {jQuery} element + */ + errorPlacement: function (error, element) { + error.insertAfter(element); + } + }).on('highlight.validate', function () { + $(this).validation('option'); + }); + newVideoForm.validation(); + + if (!newVideoForm.valid()) { + return null; + } + + inputFile = uploader; + itemVal = $('#item_id').val(); + mediaFields = $('input[name*="' + itemVal + '"]'); + $.each(mediaFields, function (itmp, el) { + var start = el.name.indexOf(itemVal) + itemVal.length + 1, + fieldName = el.name.substring(start, el.name.length - 1), + fieldItem = $('#' + fieldName); + + if (fieldItem.length > 0) { + $('input[name*="' + itemVal + '[' + fieldName + ']"]').val(fieldItem.val()); + } + }); + flagChecked = $('#new_video_disabled').prop('checked') ? 1 : 0; + $('input[name*="' + itemVal + '[disabled]"]').val(flagChecked); + + if (flagChecked) { + $('[name*="' + itemVal + '"]').siblings('.image-fade').css('visibility', 'visible'); + } else { + $('[name*="' + itemVal + '"]').siblings('.image-fade').css('visibility', 'hidden'); + } + + imageData = widget._getImage($('#file_name').val()); + fileName = $('#new_video_screenshot').get(0).files[0]; + uploader.replaceWith(inputFile); + + if (!fileName) { + callback.call(0, imageData); + widget._replaceImage(imageData.file, imageData.file, imageData); + } else { + widget._uploadImage(fileName, imageData.file, callback); + } + } + }, + { + text: $.mage.__('Delete'), + class: 'action-primary video-delete-button', + + /** + * Fired when click on delete image button + */ + click: function () { + var removed = $('[name*="' + $('#item_id').val() + '[removed]"]'); + + widget._onClose(); + removed.val(1); + removed.parent().hide(); + } + }, + { + text: $.mage.__('Cancel'), + class: 'video-cancel-button', + + /** + * Fired when click on Close dialog button + * @param {Event} e + */ + click: function (e) { + widget._onClose(e); + } + }], + + /** + * Open widged action + */ + opened: function () { + var file = $('#file_name').val(), + imageData; + + $('#video_url').focus(); + $('button[data-role="close-panel"]').click(); + + if (!file) { + return null; + } + imageData = widget._getImage(file); + widget._onPreview(null, imageData.url, false); + }, + + /** + * Closed widged action + */ + closed: function () { + var newVideoForm = $('#new_video_form'); + + if (widget._previewImage) { + widget._previewImage.remove(); + widget._previewImage = null; + } + + $(newVideoForm).trigger('reset'); + $(newVideoForm).find('input[type="hidden"][name!="form_key"]').val(''); + $('input[name*="' + $('#item_id').val() + '"]').parent().removeClass('active'); + + try { + newVideoForm.validation('clearError'); + } catch (e) { + + } + } + }); + }, + + /** + * Read image from local + * @param {String} file + * @param {Function} callback + * @private + */ + _readPreviewLocal: function (file, callback) { + var fr; + + if (!window.FileReader) { + return null; + } + + fr = new FileReader; + + /** + * Fired when image loaded + */ + fr.onloadend = function () { + callback(fr.result); + }; + fr.readAsDataURL(file); + }, + + /** + * Fired when preview input change + * @private + */ + _onImageInputChange: function () { + var file = document.getElementById('new_video_screenshot').files[0]; + + if (!file) { + return null; + } + + this._onPreview(null, file, true); + }, + + /** + * Fired when image loaded + * @param {bool} error + * @param {String} src + * @param {bool} local + * @private + */ + _onPreview: function (error, src, local) { + var img = this._getPreviewImage(), + + /** + * Callback + * @param {String} source + */ + renderImage = function (source) { + img.attr({ + 'src': source + }).show(); + }; + + if (error) { + return null; + } + + if (!local) { + renderImage(src); + } else { + this._readPreviewLocal(src, renderImage); + } + }, + + /** + * Get preview image DOM instance + * @returns {null} + * @private + */ + _getPreviewImage: function () { + + if (!this._previewImage) { + this._previewImage = $(document.createElement('img')).css({ + 'width': '145px', + 'display': 'none', + 'src': '' + }); + $(this._previewImage).insertAfter('#new_video_screenshot_preview'); + } + + return this._previewImage; + }, + + /** + * Fired when dialog close + * @private + */ + _onClose: function () { + $('#new-video').modal('closeModal'); + }, + + /** + * Find element by fileName + * @param {String} file + * @returns {*} + */ + findElementId: function (file) { + var elem = $('.image.item').find('input[value="' + file + '"]'); + + if (!elem) { + + return null; + } + + return $(elem).attr('name').replace('product[media_gallery][images][', '').replace('][file]', ''); + }, + + /** + * Save image roles + * @param {Object} imageData + */ + saveImageRoles: function (imageData) { + var data = imageData.file, + self = this, + containers; + + if (!data) { + throw new Error('You need use _getImae'); + } + + if (data.length > 0) { + containers = $('.video-placeholder').siblings('input'); + $.each(containers, function (i, el) { + var start = el.name.indexOf('[') + 1, + end = el.name.indexOf(']'), + imageType = el.name.substring(start, end), + imageCheckbox = $('input[value="' + imageType + '"]'); + + self._changeRole(imageType, imageCheckbox.attr('checked'), imageData); + }); + } + }, + + /** + * Change image role + * @param {String} imageType - role name + * @param {bool} isEnabled - role active status + * @param {Object} imageData - image data object + * @private + */ + _changeRole: function (imageType, isEnabled, imageData) { + var needCheked = true; + + if (!isEnabled) { + needCheked = $('input[name="product[' + imageType + ']"]').val() === imageData.file; + } + + if (!needCheked) { + + return null; + } + + $('#media_gallery_content').trigger('setImageType', { + type: imageType, + imageData: isEnabled ? imageData : null + }); + }, + + /** + * Register Items event listeners + */ + toggleButtons: function () { + $('.video-placeholder').click(function () { + $('.video-create-button').show(); + $('.video-delete-button').hide(); + $('.video-edit').hide(); + $('.modal-title').html('New video'); + }); + $(document).on('click', '.item.image', function () { + $('.video-create-button').hide(); + $('.video-delete-button').show(); + $('.video-edit').show(); + $('.modal-title').html('Edit video'); + }); + $(document).on('click', '.item.image', function () { + var flagChecked, + file, + formFields = $('#new_video_form').find('.edited-data'), + container = $(this); + + $.each(formFields, function (i, field) { + $(field).val(container.find('input[name*="' + field.name + '"]').val()); + }); + + flagChecked = container.find('input[name*="disabled"]').val() > 0; + $('#new_video_disabled').prop('checked', flagChecked); + + file = $('#file_name').val(container.find('input[name*="file"]').val()); + $.each($('.video_image_role'), function () { + $(this).prop('checked', false).prop('disabled', false); + }); + $.each($('.video-placeholder').siblings('input:hidden'), function () { + var start, + end, + imageRole; + + if ($(this).val() !== file.val()) { + + return null; + } + start = this.name.indexOf('[') + 1, + end = this.name.length - 1, + imageRole = this.name.substring(start, end); + $('input[value="' + imageRole + '"]').prop('checked', true); + }); + }); + } + }); + $('.video-create-button').on('click', function () { + $('#media_gallery_content').find('.video-item').parent().addClass('video-item'); + }); + + return $.mage.newVideoDialog; +}); diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/video-modal.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/video-modal.js new file mode 100644 index 0000000000000000000000000000000000000000..77221c35d1b37f1fc2298a3d0ac485bfb880b49f --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/video-modal.js @@ -0,0 +1,53 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'jquery/ui', + 'Magento_Ui/js/modal/modal', + 'mage/translate', + 'mage/backend/tree-suggest', + 'mage/backend/validation' +], function ($) { + 'use strict'; + + $.widget('mage.productGallery', + $.mage.productGallery, + { + + /** + * Fired when windget initialization start + * @private + */ + _create: function () { + this._bind(); + }, + + /** + * Bind events + * @private + */ + _bind: function () { + $(this.element).on('click', this.showModal.bind(this)); + }, + + /** + * Fired on trigger "openModal" + */ + showModal: function () { + var videoimgRoleInput = $('.video_image_role'); + + $('#new-video').modal('openModal'); + videoimgRoleInput.prop('disabled', false); + $('#new_video_form')[0].reset(); + + if ($('.image.item').length < 1) { + videoimgRoleInput.prop('checked', true); + } + } + } + ); + + return $.mage.productGallery; +}); diff --git a/app/code/Magento/Quote/Api/Data/CartItemInterface.php b/app/code/Magento/Quote/Api/Data/CartItemInterface.php index 36dbc7dea3c4c6f8713b46d7b8d58e6f62716147..f6a44a8c2d08a4994b6d482dc5f69839fd4ed9f5 100644 --- a/app/code/Magento/Quote/Api/Data/CartItemInterface.php +++ b/app/code/Magento/Quote/Api/Data/CartItemInterface.php @@ -28,6 +28,8 @@ interface CartItemInterface extends \Magento\Framework\Api\ExtensibleDataInterfa const KEY_QUOTE_ID = 'quote_id'; + const KEY_PRODUCT_OPTION = 'product_option'; + /**#@-*/ /** @@ -135,6 +137,21 @@ interface CartItemInterface extends \Magento\Framework\Api\ExtensibleDataInterfa */ public function setQuoteId($quoteId); + /** + * Returns product option + * + * @return \Magento\Quote\Api\Data\ProductOptionInterface|null + */ + public function getProductOption(); + + /** + * Sets product option + * + * @param \Magento\Quote\Api\Data\ProductOptionInterface $productOption + * @return $this + */ + public function setProductOption(\Magento\Quote\Api\Data\ProductOptionInterface $productOption); + /** * Retrieve existing extension attributes object or create a new one. * diff --git a/app/code/Magento/Quote/Api/Data/PaymentInterface.php b/app/code/Magento/Quote/Api/Data/PaymentInterface.php index 38cf9bd1513f93e2a56d3d85ff22886435e5c3f4..e1c10ed65d8ae6cc7bceed35185b106bf26f3878 100644 --- a/app/code/Magento/Quote/Api/Data/PaymentInterface.php +++ b/app/code/Magento/Quote/Api/Data/PaymentInterface.php @@ -18,8 +18,6 @@ interface PaymentInterface extends \Magento\Framework\Api\ExtensibleDataInterfac const KEY_METHOD = 'method'; - const KEY_TITLE = 'title'; - const KEY_CC_OWNER = 'cc_owner'; const KEY_CC_NUMBER = 'cc_number'; @@ -64,21 +62,6 @@ interface PaymentInterface extends \Magento\Framework\Api\ExtensibleDataInterfac */ public function setMethod($method); - /** - * Get payment method code - * - * @return string - */ - public function getTitle(); - - /** - * Set payment method code - * - * @param string $method - * @return $this - */ - public function setTitle($method); - /** * Get credit card owner * diff --git a/app/code/Magento/Quote/Api/Data/ProductOptionInterface.php b/app/code/Magento/Quote/Api/Data/ProductOptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..59ebd29b11f3053f17ed8e212b27cb3d2572f391 --- /dev/null +++ b/app/code/Magento/Quote/Api/Data/ProductOptionInterface.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Quote\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Product option interface + */ +interface ProductOptionInterface extends ExtensibleDataInterface +{ + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Quote\Api\Data\ProductOptionExtensionInterface|null + */ + public function getExtensionAttributes(); + + /** + * Set an extension attributes object. + * + * @param \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensionAttributes + ); +} diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index e9ee00aa7b0c7a14459aa220c17c081d34588aa9..f90dbc2ee65f4fe324fd5087ca4f1b72dba5eb23 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -1594,7 +1594,7 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C $this->_eventManager->dispatch('sales_quote_product_add_after', ['items' => $items]); - return $item; + return $parentItem; } /** diff --git a/app/code/Magento/Quote/Model/Quote/Item.php b/app/code/Magento/Quote/Model/Quote/Item.php index 1593b4ac3a53073a8fa4e57ecb4f392e93f1f9b8..5ead10f925544fe0e081d58505def895d0cd6850 100644 --- a/app/code/Magento/Quote/Model/Quote/Item.php +++ b/app/code/Magento/Quote/Model/Quote/Item.php @@ -10,6 +10,7 @@ namespace Magento\Quote\Model\Quote; use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\Api\ExtensionAttributesFactory; +use Magento\Quote\Api\Data\CartItemInterface; /** * Sales Quote Item Model @@ -1012,6 +1013,27 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Mage { return $this->setData(self::KEY_QUOTE_ID, $quoteId); } + + /** + * Returns product option + * + * @return \Magento\Quote\Api\Data\ProductOptionInterface|null + */ + public function getProductOption() + { + return $this->getData(self::KEY_PRODUCT_OPTION); + } + + /** + * Sets product option + * + * @param \Magento\Quote\Api\Data\ProductOptionInterface $productOption + * @return $this + */ + public function setProductOption(\Magento\Quote\Api\Data\ProductOptionInterface $productOption) + { + return $this->setData(self::KEY_PRODUCT_OPTION, $productOption); + } //@codeCoverageIgnoreEnd /** diff --git a/app/code/Magento/Quote/Model/Quote/Item/CartItemProcessorInterface.php b/app/code/Magento/Quote/Model/Quote/Item/CartItemProcessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..0f1fa2ae93ac079f63e3fb87600be735c5474058 --- /dev/null +++ b/app/code/Magento/Quote/Model/Quote/Item/CartItemProcessorInterface.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Quote\Model\Quote\Item; + +use Magento\Quote\Api\Data\CartItemInterface; + +/** + * Interface CartItemProcessorInterface + */ +interface CartItemProcessorInterface +{ + /** + * Convert cart item to buy request object + * + * @param CartItemInterface $cartItem + * @return \Magento\Framework\DataObject|null + */ + public function convertToBuyRequest(CartItemInterface $cartItem); + + /** + * Process cart item product options + * + * @param CartItemInterface $cartItem + * @return CartItemInterface + */ + public function processProductOptions(CartItemInterface $cartItem); +} diff --git a/app/code/Magento/Quote/Model/Quote/Item/Repository.php b/app/code/Magento/Quote/Model/Quote/Item/Repository.php index e79d63572f9c7aafe075dbf6235e3d845af73806..c979c5a52a4c2e3442c8de58b50f79b63699ea03 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Repository.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Repository.php @@ -9,6 +9,7 @@ namespace Magento\Quote\Model\Quote\Item; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\LocalizedException; class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface { @@ -32,20 +33,26 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface protected $itemDataFactory; /** - * Constructs a read service object. - * + * @var \Magento\Quote\Model\Quote\Item\CartItemProcessorInterface[] + */ + protected $cartItemProcessors; + + /** * @param \Magento\Quote\Model\QuoteRepository $quoteRepository * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory + * @param \Magento\Quote\Model\Quote\Item\CartItemProcessorInterface[] $cartItemProcessors */ public function __construct( \Magento\Quote\Model\QuoteRepository $quoteRepository, \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, - \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory + \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory, + array $cartItemProcessors = [] ) { $this->quoteRepository = $quoteRepository; $this->productRepository = $productRepository; $this->itemDataFactory = $itemDataFactory; + $this->cartItemProcessors = $cartItemProcessors; } /** @@ -60,7 +67,7 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface /** @var \Magento\Quote\Model\Quote\Item $item */ foreach ($quote->getAllItems() as $item) { if (!$item->isDeleted() && !$item->getParentItemId()) { - $output[] = $item; + $output[] = $this->addProductOptions($item->getProductType(), $item); } } return $output; @@ -68,6 +75,7 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface /** * {@inheritdoc} + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) { @@ -82,28 +90,78 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface $itemId = $cartItem->getItemId(); try { - /** update item qty */ + /** update item */ if (isset($itemId)) { - $cartItem = $quote->getItemById($itemId); - if (!$cartItem) { + $item = $quote->getItemById($itemId); + if (!$item) { throw new NoSuchEntityException( __('Cart %1 doesn\'t contain item %2', $cartId, $itemId) ); } - $product = $this->productRepository->get($cartItem->getSku()); - $cartItem->setData('qty', $qty); + $productType = $item->getProduct()->getTypeId(); + $buyRequestData = $this->getBuyRequest($productType, $cartItem); + if (is_object($buyRequestData)) { + /** update item product options */ + /** @var \Magento\Quote\Model\Quote\Item $cartItem */ + $cartItem = $quote->updateItem($itemId, $buyRequestData); + } else { + /** update item qty */ + $item->setData('qty', $qty); + } } else { + /** add item to shopping cart */ $product = $this->productRepository->get($cartItem->getSku()); - $quote->addProduct($product, $qty); + $productType = $product->getTypeId(); + /** @var \Magento\Quote\Model\Quote\Item|string $cartItem */ + $cartItem = $quote->addProduct($product, $this->getBuyRequest($productType, $cartItem)); + if (is_string($cartItem)) { + throw new \Magento\Framework\Exception\LocalizedException(__($cartItem)); + } } $this->quoteRepository->save($quote->collectTotals()); } catch (\Exception $e) { - if ($e instanceof NoSuchEntityException) { + if ($e instanceof NoSuchEntityException || $e instanceof LocalizedException) { throw $e; } throw new CouldNotSaveException(__('Could not save quote')); } - return $quote->getItemByProduct($product); + $itemId = $cartItem->getId(); + foreach ($quote->getAllItems() as $quoteItem) { + if ($itemId == $quoteItem->getId()) { + return $this->addProductOptions($productType, $quoteItem); + } + } + throw new CouldNotSaveException(__('Could not save quote')); + } + + /** + * @param string $productType + * @param \Magento\Quote\Api\Data\CartItemInterface $cartItem + * @return \Magento\Framework\DataObject|float + */ + protected function getBuyRequest( + $productType, + \Magento\Quote\Api\Data\CartItemInterface $cartItem + ) { + $params = (isset($this->cartItemProcessors[$productType])) + ? $this->cartItemProcessors[$productType]->convertToBuyRequest($cartItem) + : null; + return ($params === null) ? $cartItem->getQty() : $params->setQty($cartItem->getQty()); + } + + /** + * @param string $productType + * @param \Magento\Quote\Api\Data\CartItemInterface $cartItem + * @return \Magento\Quote\Api\Data\CartItemInterface + */ + protected function addProductOptions( + $productType, + \Magento\Quote\Api\Data\CartItemInterface $cartItem + ) { + $cartItem = (isset($this->cartItemProcessors[$productType])) + ? $this->cartItemProcessors[$productType]->processProductOptions($cartItem) + : $cartItem; + return $cartItem; } /** @@ -111,11 +169,7 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface */ public function deleteById($cartId, $itemId) { - /** - * Quote. - * - * @var \Magento\Quote\Model\Quote $quote - */ + /** @var \Magento\Quote\Model\Quote $quote */ $quote = $this->quoteRepository->getActive($cartId); $quoteItem = $quote->getItemById($itemId); if (!$quoteItem) { diff --git a/app/code/Magento/Quote/Model/Quote/Payment.php b/app/code/Magento/Quote/Model/Quote/Payment.php index ae74fd09ea52d100c3a0a6f6e1141a9087b2c75b..e11fe99e3d949eb5d594decd26e7023cbb4dce18 100644 --- a/app/code/Magento/Quote/Model/Quote/Payment.php +++ b/app/code/Magento/Quote/Model/Quote/Payment.php @@ -5,8 +5,6 @@ */ namespace Magento\Quote\Model\Quote; -use Magento\Payment\Model\Method\AbstractMethod; - /** * Quote payment information * @@ -275,27 +273,6 @@ class Payment extends \Magento\Payment\Model\Info implements \Magento\Quote\Api\ return $this->setData(self::KEY_METHOD, $method); } - /** - * Get payment title - * - * @return string - */ - public function getTitle() - { - return $this->getData(self::KEY_TITLE); - } - - /** - * Set payment title - * - * @param string $title - * @return $this - */ - public function setTitle($title) - { - return $this->setData(self::KEY_TITLE, $title); - } - /** * Get credit card owner * diff --git a/app/code/Magento/Quote/Model/Quote/ProductOption.php b/app/code/Magento/Quote/Model/Quote/ProductOption.php new file mode 100644 index 0000000000000000000000000000000000000000..912f634fd14d16130370e5946a0f03865a4831ac --- /dev/null +++ b/app/code/Magento/Quote/Model/Quote/ProductOption.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Quote\Model\Quote; + +use Magento\Framework\Model\AbstractExtensibleModel; +use Magento\Quote\Api\Data\ProductOptionInterface; + +/** + * Product option entity + */ +class ProductOption extends AbstractExtensibleModel implements ProductOptionInterface +{ + /** + * Retrieve existing extension attributes object or create a new one. + * + * @return \Magento\Quote\Api\Data\ProductOptionExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * Set an extension attributes object. + * + * @param \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \Magento\Quote\Api\Data\ProductOptionExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 7c54f986af74ec71c5ae2018636ff73d9ae6a564..490e894d7a51f2f53a634701df3c2f8372608552 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -467,8 +467,9 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface $this->eventManager->dispatch( 'sales_model_service_quote_submit_failure', [ - 'order' => $order, - 'quote' => $quote + 'order' => $order, + 'quote' => $quote, + 'exception' => $e ] ); throw $e; diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php index 8802f9be05add635739e42732d481cae1f255797..7836d91000c5baf03915d18c1bdad16b4e70fc2b 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php @@ -62,11 +62,12 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $this->getMock('Magento\Catalog\Api\ProductRepositoryInterface', [], [], '', false); $this->itemDataFactoryMock = $this->getMock('Magento\Quote\Api\Data\CartItemInterfaceFactory', ['create'], [], '', false); - $this->dataMock = $this->getMock('Magento\Quote\Api\Data\CartItemInterface'); + $this->dataMock = $this->getMock('Magento\Quote\Model\Quote\Item', [], [], '', false); $this->quoteMock = $this->getMock('\Magento\Quote\Model\Quote', [], [], '', false); $this->productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false); + $methods = ['getId', 'getSku', 'getQty', 'setData', '__wakeUp', 'getProduct', 'addProduct']; $this->quoteItemMock = - $this->getMock('Magento\Quote\Model\Quote\Item', ['getId', 'getSku', 'setData', '__wakeUp'], [], '', false); + $this->getMock('Magento\Quote\Model\Quote\Item', $methods, [], '', false); $this->repository = new Repository( $this->quoteRepositoryMock, @@ -104,6 +105,35 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase ]; } + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Please specify all the required information. + */ + public function testSaveCouldNotAddProduct() + { + $cartId = 13; + $this->dataMock->expects($this->exactly(2))->method('getQty')->will($this->returnValue(12)); + $this->dataMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); + $this->quoteRepositoryMock->expects($this->once()) + ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); + $this->productRepositoryMock->expects($this->once()) + ->method('get') + ->will($this->returnValue($this->productMock)); + $this->dataMock->expects($this->once())->method('getSku'); + $this->quoteMock + ->expects($this->once()) + ->method('addProduct') + ->with($this->productMock, 12) + ->willReturn('Please specify all the required information.'); + $this->quoteMock->expects($this->never())->method('getItemById'); + $this->quoteRepositoryMock->expects($this->never())->method('save')->with($this->quoteMock); + $this->quoteMock + ->expects($this->never()) + ->method('getAllItems'); + $this->repository->save($this->dataMock); + } + /** * @return void * @expectedException \Magento\Framework\Exception\CouldNotSaveException @@ -112,34 +142,41 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase public function testSaveCouldNotSaveException() { $cartId = 13; - $this->dataMock->expects($this->once())->method('getQty')->will($this->returnValue(12)); + $this->dataMock->expects($this->exactly(2))->method('getQty')->will($this->returnValue(12)); $this->dataMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); - $this->quoteRepositoryMock->expects($this->once())->method('getActive') - ->with($cartId)->will($this->returnValue($this->quoteMock)); - $this->dataMock->expects($this->once())->method('getSku')->will($this->returnValue('product_sku')); - $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue(null)); - $this->quoteMock->expects($this->never())->method('getItemById'); + $this->quoteRepositoryMock->expects($this->once()) + ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->productRepositoryMock->expects($this->once()) - ->method('get')->with('product_sku')->will($this->returnValue($this->productMock)); - $this->quoteMock->expects($this->once())->method('addProduct')->with($this->productMock, 12); + ->method('get') + ->will($this->returnValue($this->productMock)); + $this->dataMock->expects($this->once())->method('getSku'); + $this->quoteMock + ->expects($this->once()) + ->method('addProduct') + ->with($this->productMock, 12) + ->willReturn($this->productMock); + $this->quoteMock->expects($this->never())->method('getItemById'); $this->quoteMock->expects($this->once())->method('collectTotals')->will($this->returnValue($this->quoteMock)); + $this->quoteRepositoryMock->expects($this->once())->method('save')->with($this->quoteMock); + $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue(null)); $exceptionMessage = 'Could not save quote'; $exception = new \Magento\Framework\Exception\CouldNotSaveException(__($exceptionMessage)); $this->quoteRepositoryMock->expects($this->once()) ->method('save') ->with($this->quoteMock) ->willThrowException($exception); - + $this->quoteMock + ->expects($this->never()) + ->method('getAllItems'); $this->repository->save($this->dataMock); } - /** * @return void */ public function testSave() { $cartId = 13; - $this->dataMock->expects($this->once())->method('getQty')->will($this->returnValue(12)); + $this->dataMock->expects($this->exactly(2))->method('getQty')->will($this->returnValue(12)); $this->dataMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); @@ -147,16 +184,20 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase ->method('get') ->will($this->returnValue($this->productMock)); $this->dataMock->expects($this->once())->method('getSku'); - $this->quoteMock->expects($this->once())->method('addProduct')->with($this->productMock, 12); + $this->quoteMock + ->expects($this->once()) + ->method('addProduct') + ->with($this->productMock, 12) + ->willReturn($this->productMock); $this->quoteMock->expects($this->never())->method('getItemById'); $this->quoteMock->expects($this->once())->method('collectTotals')->will($this->returnValue($this->quoteMock)); $this->quoteRepositoryMock->expects($this->once())->method('save')->with($this->quoteMock); + $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue(null)); $this->quoteMock ->expects($this->once()) - ->method('getItemByProduct') - ->with($this->productMock) - ->will($this->returnValue($this->quoteItemMock)); - $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue(null)); + ->method('getAllItems') + ->willReturn([$this->quoteItemMock]); + $this->quoteItemMock->expects($this->any())->method('getId'); $this->assertEquals($this->quoteItemMock, $this->repository->save($this->dataMock)); } @@ -191,23 +232,24 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase { $cartId = 11; $itemId = 5; - $productSku = 'product_sku'; - $this->dataMock->expects($this->once())->method('getQty')->will($this->returnValue(12)); + $this->dataMock->expects($this->exactly(2))->method('getQty')->will($this->returnValue(12)); $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue($itemId)); $this->dataMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->quoteMock->expects($this->once()) ->method('getItemById')->with($itemId)->will($this->returnValue($this->quoteItemMock)); + $this->quoteItemMock->expects($this->any())->method('getProduct')->willReturn($this->productMock); + $this->productMock->expects($this->once())->method('getTypeId')->willReturn('simple'); $this->quoteItemMock->expects($this->once())->method('setData')->with('qty', 12); - $this->quoteItemMock->expects($this->once())->method('getSku')->willReturn($productSku); $this->productRepositoryMock - ->expects($this->once()) - ->method('get') - ->with($productSku) - ->willReturn($this->productMock); - $this->quoteMock->expects($this->once())->method('collectTotals')->will($this->returnValue($this->quoteMock)); + ->expects($this->never())->method('get'); $this->quoteItemMock->expects($this->never())->method('addProduct'); + $this->quoteMock->expects($this->once())->method('collectTotals')->will($this->returnValue($this->quoteMock)); + $this->quoteMock + ->expects($this->never()) + ->method('getAllItems'); + $this->quoteItemMock->expects($this->never())->method('getId'); $exceptionMessage = 'Could not save quote'; $exception = new \Magento\Framework\Exception\CouldNotSaveException(__($exceptionMessage)); $this->quoteRepositoryMock->expects($this->once()) @@ -221,33 +263,79 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase /** * @return void */ - public function testUpdateItem() + public function testUpdateItemQty() { $cartId = 11; $itemId = 5; - $productSku = 'product_sku'; - $this->dataMock->expects($this->once())->method('getQty')->will($this->returnValue(12)); + $this->dataMock->expects($this->exactly(2))->method('getQty')->will($this->returnValue(12)); $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue($itemId)); $this->dataMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); + $this->dataMock->expects($this->once())->method('getId')->willReturn($itemId); $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->quoteMock->expects($this->once()) ->method('getItemById')->with($itemId)->will($this->returnValue($this->quoteItemMock)); + $this->quoteItemMock->expects($this->once())->method('getProduct')->willReturn($this->productMock); + $this->productMock->expects($this->once())->method('getTypeId')->willReturn('simple'); $this->quoteItemMock->expects($this->once())->method('setData')->with('qty', 12); - $this->quoteItemMock->expects($this->once())->method('getSku')->willReturn($productSku); $this->productRepositoryMock - ->expects($this->once()) - ->method('get') - ->with($productSku) - ->willReturn($this->productMock); + ->expects($this->never())->method('get'); $this->quoteItemMock->expects($this->never())->method('addProduct'); $this->quoteMock->expects($this->once())->method('collectTotals')->will($this->returnValue($this->quoteMock)); $this->quoteRepositoryMock->expects($this->once())->method('save')->with($this->quoteMock); $this->quoteMock ->expects($this->once()) - ->method('getItemByProduct') - ->with($this->productMock) + ->method('getAllItems') + ->willReturn([$this->quoteItemMock]); + $this->quoteItemMock->expects($this->any())->method('getId')->willReturn($itemId); + $this->assertEquals($this->quoteItemMock, $this->repository->save($this->dataMock)); + } + + /** + * @return void + */ + public function testUpdateItemOptions() + { + $cartId = 11; + $itemId = 5; + $cartItemProcessorMock = $this->getMock('\Magento\Quote\Model\Quote\Item\CartItemProcessorInterface'); + $this->repository = new Repository( + $this->quoteRepositoryMock, + $this->productRepositoryMock, + $this->itemDataFactoryMock, + ['simple' => $cartItemProcessorMock] + ); + $requestMock = $this->getMock('\Magento\Framework\DataObject', ['setQty'], [], '', false); + $cartItemProcessorMock->expects($this->once())->method('convertToBuyRequest')->willReturn($requestMock); + $cartItemProcessorMock + ->expects($this->once()) + ->method('processProductOptions') ->willReturn($this->quoteItemMock); + $requestMock->expects($this->once())->method('setQty')->with(12)->willReturnSelf(); + $this->quoteMock + ->expects($this->once()) + ->method('updateItem') + ->with($itemId, $requestMock) + ->willReturn($this->quoteItemMock); + $this->dataMock->expects($this->any())->method('getQty')->will($this->returnValue(12)); + $this->dataMock->expects($this->once())->method('getItemId')->will($this->returnValue($itemId)); + $this->dataMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); + $this->quoteRepositoryMock->expects($this->once()) + ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); + $this->quoteMock->expects($this->once()) + ->method('getItemById')->with($itemId)->will($this->returnValue($this->quoteItemMock)); + $this->quoteItemMock->expects($this->once())->method('getProduct')->willReturn($this->productMock); + $this->productMock->expects($this->once())->method('getTypeId')->willReturn('simple'); + $this->productRepositoryMock + ->expects($this->never())->method('get'); + $this->quoteMock->expects($this->once())->method('collectTotals')->will($this->returnValue($this->quoteMock)); + $this->quoteRepositoryMock->expects($this->once())->method('save')->with($this->quoteMock); + $this->quoteMock + ->expects($this->once()) + ->method('getAllItems') + ->willReturn([$this->quoteItemMock]); + $this->quoteItemMock->expects($this->any())->method('getId')->willReturn($itemId); + $this->quoteItemMock->expects($this->any())->method('getQty')->willReturn(12); $this->assertEquals($this->quoteItemMock, $this->repository->save($this->dataMock)); } diff --git a/app/code/Magento/Quote/etc/di.xml b/app/code/Magento/Quote/etc/di.xml index def3e65b6b89cc4b424dea208082dfc85d198069..3b10c9cdd3993e3f9211f8dd12e26325c7ad401f 100644 --- a/app/code/Magento/Quote/etc/di.xml +++ b/app/code/Magento/Quote/etc/di.xml @@ -38,6 +38,7 @@ <preference for="Magento\Quote\Api\GuestBillingAddressManagementInterface" type="Magento\Quote\Model\GuestCart\GuestBillingAddressManagement" /> <preference for="Magento\Quote\Api\GuestCartTotalManagementInterface" type="\Magento\Quote\Model\GuestCart\GuestCartTotalManagement" /> <preference for="Magento\Quote\Api\Data\EstimateAddressInterface" type="Magento\Quote\Model\EstimateAddress" /> + <preference for="Magento\Quote\Api\Data\ProductOptionInterface" type="\Magento\Quote\Model\Quote\ProductOption" /> <type name="Magento\Webapi\Controller\Rest\ParamsOverrider"> <arguments> <argument name="paramOverriders" xsi:type="array"> diff --git a/app/code/Magento/Quote/etc/fieldset.xml b/app/code/Magento/Quote/etc/fieldset.xml index 4be04d06516414c73923f4d48b04de409a4c47a7..aa695aadc4214d3f89f454ee00385157fa212a88 100644 --- a/app/code/Magento/Quote/etc/fieldset.xml +++ b/app/code/Magento/Quote/etc/fieldset.xml @@ -157,6 +157,26 @@ <aspect name="to_order_address" /> <aspect name="to_customer_address" /> </field> + <field name="vat_id"> + <aspect name="to_order_address" /> + <aspect name="to_customer_address" /> + </field> + <field name="vat_is_valid"> + <aspect name="to_order_address" /> + <aspect name="to_customer_address" /> + </field> + <field name="vat_request_id"> + <aspect name="to_order_address" /> + <aspect name="to_customer_address" /> + </field> + <field name="vat_request_date"> + <aspect name="to_order_address" /> + <aspect name="to_customer_address" /> + </field> + <field name="vat_request_success"> + <aspect name="to_order_address" /> + <aspect name="to_customer_address" /> + </field> <field name="email"> <aspect name="to_order" targetField="customer_email"/> <aspect name="to_order_address" /> diff --git a/app/code/Magento/RequireJs/Model/FileManager.php b/app/code/Magento/RequireJs/Model/FileManager.php index 6ce74ef2139b1232879b6ffafa20c46017cbae53..8ca3298865efcaa9a4354af6db10ecfbd8fe0616 100644 --- a/app/code/Magento/RequireJs/Model/FileManager.php +++ b/app/code/Magento/RequireJs/Model/FileManager.php @@ -171,4 +171,18 @@ class FileManager return $bundles; } + + /** + * Remove all bundles from pool + * + * @return bool + */ + public function clearBundleJsPool() + { + $dirWrite = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); + /** @var $context \Magento\Framework\View\Asset\File\FallbackContext */ + $context = $this->assetRepo->getStaticViewFileContext(); + $bundleDir = $context->getPath() . '/' . Config::BUNDLE_JS_DIR; + return $dirWrite->delete($bundleDir); + } } diff --git a/app/code/Magento/RequireJs/Test/Unit/Model/FileManagerTest.php b/app/code/Magento/RequireJs/Test/Unit/Model/FileManagerTest.php index 0993494d09f57a185e1fca3fee9f70717dcb5f6c..1d89833a73b7f26c6fffba5e6d69ff3a9aef91eb 100644 --- a/app/code/Magento/RequireJs/Test/Unit/Model/FileManagerTest.php +++ b/app/code/Magento/RequireJs/Test/Unit/Model/FileManagerTest.php @@ -223,4 +223,27 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase $this->assertSame($this->asset, $this->object->createRequireJsMixinsAsset()); } + + public function testClearBundleJsPool() + { + $context = $this->getMockBuilder('Magento\Framework\View\Asset\File\FallbackContext') + ->disableOriginalConstructor() + ->getMock(); + $this->fileSystem->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::STATIC_VIEW) + ->willReturn($this->dir); + $this->assetRepoMock + ->expects($this->once()) + ->method('getStaticViewFileContext') + ->willReturn($context); + $context->expects($this->once()) + ->method('getPath') + ->willReturn('/path/to/directory'); + $this->dir->expects($this->once()) + ->method('delete') + ->with('/path/to/directory/' . \Magento\Framework\RequireJs\Config::BUNDLE_JS_DIR) + ->willReturn(true); + $this->assertTrue($this->object->clearBundleJsPool()); + } } diff --git a/app/code/Magento/Rule/Model/AbstractModel.php b/app/code/Magento/Rule/Model/AbstractModel.php index 988a9e2beca59c3b12da13a0123e2b7b858778a6..20bb67ca0632c708946d9961f22099b1f810128a 100644 --- a/app/code/Magento/Rule/Model/AbstractModel.php +++ b/app/code/Magento/Rule/Model/AbstractModel.php @@ -350,19 +350,19 @@ abstract class AbstractModel extends \Magento\Framework\Model\AbstractModel /** * Validate rule data * - * @param \Magento\Framework\DataObject $object + * @param \Magento\Framework\DataObject $dataObject * @return bool|string[] - return true if validation passed successfully. Array with errors description otherwise * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function validateData(\Magento\Framework\DataObject $object) + public function validateData(\Magento\Framework\DataObject $dataObject) { $result = []; $fromDate = $toDate = null; - if ($object->hasFromDate() && $object->hasToDate()) { - $fromDate = $object->getFromDate(); - $toDate = $object->getToDate(); + if ($dataObject->hasFromDate() && $dataObject->hasToDate()) { + $fromDate = $dataObject->getFromDate(); + $toDate = $dataObject->getToDate(); } if ($fromDate && $toDate) { @@ -374,14 +374,14 @@ abstract class AbstractModel extends \Magento\Framework\Model\AbstractModel } } - if ($object->hasWebsiteIds()) { - $websiteIds = $object->getWebsiteIds(); + if ($dataObject->hasWebsiteIds()) { + $websiteIds = $dataObject->getWebsiteIds(); if (empty($websiteIds)) { $result[] = __('Please specify a website.'); } } - if ($object->hasCustomerGroupIds()) { - $customerGroupIds = $object->getCustomerGroupIds(); + if ($dataObject->hasCustomerGroupIds()) { + $customerGroupIds = $dataObject->getCustomerGroupIds(); if (empty($customerGroupIds)) { $result[] = __('Please specify Customer Groups.'); } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Report/Filter/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Report/Filter/Form.php index b7aefa2d344ef1d56b5a9797092e1542bb43a382..340a9501ef57b7eed9fe68e2024cb98ec9421f78 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Report/Filter/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Report/Filter/Form.php @@ -67,7 +67,7 @@ class Form extends \Magento\Reports\Block\Adminhtml\Filter\Form 'name' => 'show_order_statuses', 'label' => __('Order Status'), 'options' => ['0' => __('Any'), '1' => __('Specified')], - 'note' => __('Applies to Any of the Specified Order Statuses') + 'note' => __('Applies to Any of the Specified Order Statuses except canceled orders') ], 'to' ); diff --git a/app/code/Magento/Sales/Model/Order/CustomerManagement.php b/app/code/Magento/Sales/Model/Order/CustomerManagement.php index 0a5b3ee6ccbf39c979c9f6290c30f5edcf0c00de..32ef6bb7ebbf57daafe0688c5391f84ff5c4601f 100644 --- a/app/code/Magento/Sales/Model/Order/CustomerManagement.php +++ b/app/code/Magento/Sales/Model/Order/CustomerManagement.php @@ -6,6 +6,7 @@ namespace Magento\Sales\Model\Order; use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Quote\Model\Quote\Address as QuoteAddress; /** * Class CustomerManagement @@ -91,6 +92,15 @@ class CustomerManagement implements \Magento\Sales\Api\OrderCustomerManagementIn ); /** @var \Magento\Customer\Api\Data\AddressInterface $customerAddress */ $customerAddress = $this->addressFactory->create(['data' => $addressData]); + switch ($address->getAddressType()) { + case QuoteAddress::ADDRESS_TYPE_BILLING: + $customerAddress->setIsDefaultBilling(true); + break; + case QuoteAddress::ADDRESS_TYPE_SHIPPING: + $customerAddress->setIsDefaultShipping(true); + break; + } + if (is_string($address->getRegion())) { /** @var \Magento\Customer\Api\Data\RegionInterface $region */ $region = $this->regionFactory->create(); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerManagementTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerManagementTest.php index 62ab12af42dda52141338fb41e191a469a298005..4485d5ef43e83c8832041f478b5e764f041558d9 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerManagementTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerManagementTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Test\Unit\Model\Order; +use Magento\Quote\Model\Quote\Address; + /** * Class BuilderTest */ @@ -98,18 +100,40 @@ class CustomerManagementTest extends \PHPUnit_Framework_TestCase $orderMock = $this->getMock('\Magento\Sales\Api\Data\OrderInterface'); $orderMock->expects($this->once())->method('getCustomerId')->will($this->returnValue(null)); $orderMock->expects($this->any())->method('getBillingAddress')->will($this->returnValue('billing_address')); - $orderAddress = $this->getMock('\Magento\Sales\Api\Data\OrderAddressInterface'); - $addresses = [$orderAddress, $orderAddress]; - $orderMock->expects($this->any())->method('getAddresses')->will($this->returnValue($addresses)); + $orderBillingAddress = $this->getMock('\Magento\Sales\Api\Data\OrderAddressInterface'); + $orderBillingAddress->expects($this->once()) + ->method('getAddressType') + ->willReturn(Address::ADDRESS_TYPE_BILLING); + + $orderShippingAddress = $this->getMock('\Magento\Sales\Api\Data\OrderAddressInterface'); + $orderShippingAddress->expects($this->once()) + ->method('getAddressType') + ->willReturn(Address::ADDRESS_TYPE_SHIPPING); + + $orderMock->expects($this->any()) + ->method('getAddresses') + ->will($this->returnValue([$orderBillingAddress, $orderShippingAddress])); + $this->orderRepository->expects($this->once())->method('get')->with(1)->will($this->returnValue($orderMock)); $this->objectCopyService->expects($this->any())->method('copyFieldsetToTarget')->will($this->returnValueMap( [ ['order_address', 'to_customer', 'billing_address', [], 'global', ['customer_data' => []]], - ['order_address', 'to_customer_address', $orderAddress, [], 'global', 'address_data'] + ['order_address', 'to_customer_address', $orderBillingAddress, [], 'global', 'address_data'], + ['order_address', 'to_customer_address', $orderShippingAddress, [], 'global', 'address_data'], ] )); - $addressMock = $this->getMock('\Magento\Customer\Api\Data\CustomerAddressInterface'); + + $addressMock = $this->getMock('\Magento\Customer\Api\Data\AddressInterface'); + $addressMock->expects($this->any()) + ->method('setIsDefaultBilling') + ->with(true) + ->willReturnSelf(); + $addressMock->expects($this->any()) + ->method('setIsDefaultShipping') + ->with(true) + ->willReturnSelf(); + $this->addressFactory->expects($this->any())->method('create')->with(['data' => 'address_data'])->will( $this->returnValue($addressMock) ); @@ -124,6 +148,5 @@ class CustomerManagementTest extends \PHPUnit_Framework_TestCase $orderMock->expects($this->once())->method('setCustomerId')->with('customer_id'); $this->orderRepository->expects($this->once())->method('save')->with($orderMock); $this->assertEquals($customerMock, $this->service->create(1)); - } } diff --git a/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml b/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml index 78d2a5a607f27805dde9f286dc76b89d3d438713..3062b8bb2ed42881209d9f4e17ce82dc0bef269f 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/recent.phtml @@ -30,7 +30,7 @@ <th scope="col" class="col shipping"><?php /* @escapeNotVerified */ echo __('Ship To') ?></th> <th scope="col" class="col total"><?php /* @escapeNotVerified */ echo __('Order Total') ?></th> <th scope="col" class="col status"><?php /* @escapeNotVerified */ echo __('Status') ?></th> - <th scope="col" class="col actions"> </th> + <th scope="col" class="col actions"><?php /* @escapeNotVerified */ echo __('Action') ?></th> </tr> </thead> <tbody> diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html index 496008e249b3ff391a09ce40753f71cf980eeff8..5ee5d48c62e6858c47db1971eb80dfe67ae26460 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html @@ -8,7 +8,7 @@ data-bind="mageInit: {'collapsible':{'openedState': '_active'}}"> <div class="payment-option-title field choice" data-role="title"> <span class="action action-toggle" id="block-discount-heading" role="heading" aria-level="2"> - <!-- ko i18n: 'Apply Promo Code'--><!-- /ko --> + <!-- ko i18n: 'Apply Discount Code'--><!-- /ko --> </span> </div> <div class="payment-option-content" data-role="content"> @@ -19,7 +19,7 @@ <div class="payment-option-inner"> <div class="field"> <label class="label" for="discount-code"> - <span data-bind="i18n: 'Enter promo code'"></span> + <span data-bind="i18n: 'Enter discount code'"></span> </label> <div class="control"> <input class="input-text" @@ -27,15 +27,15 @@ id="discount-code" name="discount_code" data-validate="{'required-entry':true}" - data-bind="value: couponCode, attr:{placeholder: $t('Enter promo code')} " /> + data-bind="value: couponCode, attr:{placeholder: $t('Enter discount code')} " /> </div> </div> </div> <div class="actions-toolbar"> <div class="primary"> <!-- ko ifnot: isApplied() --> - <button class="action action-apply" type="submit" data-bind="'value': $t('Apply'), click: apply"> - <span><!-- ko i18n: 'Apply'--><!-- /ko --></span> + <button class="action action-apply" type="submit" data-bind="'value': $t('Apply Discount'), click: apply"> + <span><!-- ko i18n: 'Apply Discount'--><!-- /ko --></span> </button> <!-- /ko --> <!-- ko if: isApplied() --> diff --git a/app/code/Magento/Shipping/Model/Shipping/Labels.php b/app/code/Magento/Shipping/Model/Shipping/Labels.php index 37cb4b6715a9d30d290e195be3c958741269acc2..4b697f3ba15bed0635408ba2708faa34176d7a1f 100644 --- a/app/code/Magento/Shipping/Model/Shipping/Labels.php +++ b/app/code/Magento/Shipping/Model/Shipping/Labels.php @@ -8,7 +8,13 @@ namespace Magento\Shipping\Model\Shipping; +use Magento\Framework\DataObject; +use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Model\Order\Shipment; +use Magento\Sales\Model\Order\Address; +use Magento\Shipping\Model\Shipment\Request; +use Magento\Store\Model\ScopeInterface; +use Magento\User\Model\User; /** * Shipping labels model @@ -51,7 +57,7 @@ class Labels extends \Magento\Shipping\Model\Shipping \Magento\Framework\Math\Division $mathDivision, \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, \Magento\Backend\Model\Auth\Session $authSession, - \Magento\Shipping\Model\Shipment\Request $request + Request $request ) { $this->_authSession = $authSession; $this->_request = $request; @@ -81,41 +87,32 @@ class Labels extends \Magento\Shipping\Model\Shipping { $admin = $this->_authSession->getUser(); $order = $orderShipment->getOrder(); - $address = $order->getShippingAddress(); + $shippingMethod = $order->getShippingMethod(true); $shipmentStoreId = $orderShipment->getStoreId(); $shipmentCarrier = $this->_carrierFactory->create($order->getShippingMethod(true)->getCarrierCode()); $baseCurrencyCode = $this->_storeManager->getStore($shipmentStoreId)->getBaseCurrencyCode(); if (!$shipmentCarrier) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Invalid carrier: %1' , $shippingMethod->getCarrierCode()) - ); + throw new LocalizedException(__('Invalid carrier: %1', $shippingMethod->getCarrierCode())); } $shipperRegionCode = $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_REGION_ID, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ); if (is_numeric($shipperRegionCode)) { $shipperRegionCode = $this->_regionFactory->create()->load($shipperRegionCode)->getCode(); } - $recipientRegionCode = $this->_regionFactory->create()->load($address->getRegionId())->getCode(); - $originStreet1 = $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_ADDRESS1, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $shipmentStoreId - ); - $originStreet2 = $this->_scopeConfig->getValue( - Shipment::XML_PATH_STORE_ADDRESS2, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ); - $storeInfo = new \Magento\Framework\DataObject( + $storeInfo = new DataObject( (array)$this->_scopeConfig->getValue( 'general/store_information', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) ); @@ -128,21 +125,21 @@ class Labels extends \Magento\Shipping\Model\Shipping || !$shipperRegionCode || !$this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_CITY, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) || !$this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_ZIP, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) || !$this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_COUNTRY_ID, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __( 'We don\'t have enough information to create shipping labels. Please make sure your store information and settings are complete.' ) @@ -152,37 +149,87 @@ class Labels extends \Magento\Shipping\Model\Shipping /** @var $request \Magento\Shipping\Model\Shipment\Request */ $request = $this->_shipmentRequestFactory->create(); $request->setOrderShipment($orderShipment); - $request->setShipperContactPersonName($admin->getName()); - $request->setShipperContactPersonFirstName($admin->getFirstname()); - $request->setShipperContactPersonLastName($admin->getLastname()); - $request->setShipperContactCompanyName($storeInfo->getName()); - $request->setShipperContactPhoneNumber($storeInfo->getPhone()); - $request->setShipperEmail($admin->getEmail()); - $request->setShipperAddressStreet(trim($originStreet1 . ' ' . $originStreet2)); - $request->setShipperAddressStreet1($originStreet1); + $address = $order->getShippingAddress(); + + $this->setShipperDetails($request, $admin, $storeInfo, $shipmentStoreId, $shipperRegionCode, $originStreet1); + $this->setRecipientDetails($request, $address); + + $request->setShippingMethod($shippingMethod->getMethod()); + $request->setPackageWeight($order->getWeight()); + $request->setPackages($orderShipment->getPackages()); + $request->setBaseCurrencyCode($baseCurrencyCode); + $request->setStoreId($shipmentStoreId); + + return $shipmentCarrier->requestToShipment($request); + } + + /** + * Set shipper details into request + * @param \Magento\Shipping\Model\Shipment\Request $request + * @param \Magento\User\Model\User $storeAdmin + * @param \Magento\Framework\DataObject $store + * @param $shipmentStoreId + * @param $regionCode + * @param $originStreet + * @return void + */ + protected function setShipperDetails( + Request $request, + User $storeAdmin, + DataObject $store, + $shipmentStoreId, + $regionCode, + $originStreet + ) { + $originStreet2 = $this->_scopeConfig->getValue( + Shipment::XML_PATH_STORE_ADDRESS2, + ScopeInterface::SCOPE_STORE, + $shipmentStoreId + ); + + $request->setShipperContactPersonName($storeAdmin->getName()); + $request->setShipperContactPersonFirstName($storeAdmin->getFirstname()); + $request->setShipperContactPersonLastName($storeAdmin->getLastname()); + $request->setShipperContactCompanyName($store->getName()); + $request->setShipperContactPhoneNumber($store->getPhone()); + $request->setShipperEmail($storeAdmin->getEmail()); + $request->setShipperAddressStreet(trim($originStreet . ' ' . $originStreet2)); + $request->setShipperAddressStreet1($originStreet); $request->setShipperAddressStreet2($originStreet2); $request->setShipperAddressCity( $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_CITY, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) ); - $request->setShipperAddressStateOrProvinceCode($shipperRegionCode); + $request->setShipperAddressStateOrProvinceCode($regionCode); $request->setShipperAddressPostalCode( $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_ZIP, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) ); $request->setShipperAddressCountryCode( $this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_COUNTRY_ID, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + ScopeInterface::SCOPE_STORE, $shipmentStoreId ) ); + } + + /** + * Set recipient details into request + * @param \Magento\Shipping\Model\Shipment\Request $request + * @param \Magento\Sales\Model\Order\Address $address + * @return void + */ + protected function setRecipientDetails(Request $request, Address $address) + { + $recipientRegionCode = $this->_regionFactory->create()->load($address->getRegionId())->getCode(); + $request->setRecipientContactPersonName(trim($address->getFirstname() . ' ' . $address->getLastname())); $request->setRecipientContactPersonFirstName($address->getFirstname()); $request->setRecipientContactPersonLastName($address->getLastname()); @@ -193,16 +240,9 @@ class Labels extends \Magento\Shipping\Model\Shipping $request->setRecipientAddressStreet1($address->getStreetLine(1)); $request->setRecipientAddressStreet2($address->getStreetLine(2)); $request->setRecipientAddressCity($address->getCity()); - $request->setRecipientAddressStateOrProvinceCode($address->getRegionCode()); + $request->setRecipientAddressStateOrProvinceCode($recipientRegionCode ?: $address->getRegionCode()); $request->setRecipientAddressRegionCode($recipientRegionCode); $request->setRecipientAddressPostalCode($address->getPostcode()); $request->setRecipientAddressCountryCode($address->getCountryId()); - $request->setShippingMethod($shippingMethod->getMethod()); - $request->setPackageWeight($order->getWeight()); - $request->setPackages($orderShipment->getPackages()); - $request->setBaseCurrencyCode($baseCurrencyCode); - $request->setStoreId($shipmentStoreId); - - return $shipmentCarrier->requestToShipment($request); } } diff --git a/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php b/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9e4d8eede79b28f08f4ffea9f8fdddf6b02baa66 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php @@ -0,0 +1,248 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Shipping\Test\Unit\Model\Shipping; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\DataObject; +use Magento\Sales\Model\Order\Shipment; +use Magento\Store\Model\ScopeInterface; + +/** + * Class LabelsTest + * + * Test class for \Magento\Shipping\Model\Shipping\Labels + */ +class LabelsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Shipping\Model\Shipping\Labels + */ + protected $labels; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $scopeConfig; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $region; + + protected function setUp() + { + $this->request = $this->getMockBuilder('Magento\Shipping\Model\Shipment\Request') + ->disableOriginalConstructor() + ->getMock(); + $requestFactory = $this->getMockBuilder('Magento\Shipping\Model\Shipment\RequestFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $requestFactory->expects(static::any())->method('create')->willReturn($this->request); + + $carrier = $this->getMockBuilder('Magento\Shipping\Model\Carrier\AbstractCarrier') + ->disableOriginalConstructor() + ->getMock(); + + $carrierFactory = $this->getMockBuilder('Magento\Shipping\Model\CarrierFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $carrierFactory->expects(static::any())->method('create')->willReturn($carrier); + + $storeManager = $this->getStoreManager(); + $authSession = $this->getAuthSession(); + $regionFactory = $this->getRegionFactory(); + + $this->scopeConfig = $this->getMockBuilder('\Magento\Framework\App\Config') + ->disableOriginalConstructor() + ->setMethods(['getValue']) + ->getMock(); + + $objectManagerHelper = new ObjectManager($this); + $this->labels = $objectManagerHelper->getObject('Magento\Shipping\Model\Shipping\Labels', [ + 'shipmentRequestFactory' => $requestFactory, + 'carrierFactory' => $carrierFactory, + 'storeManager' => $storeManager, + 'scopeConfig' => $this->scopeConfig, + 'authSession' => $authSession, + 'regionFactory' => $regionFactory + ]); + } + + /** + * @covers \Magento\Shipping\Model\Shipping\Labels + */ + public function testRequestToShipment() + { + $order = $this->getMockBuilder('Magento\Sales\Model\Order') + ->disableOriginalConstructor() + ->getMock(); + + $shippingMethod = $this->getMockBuilder('Magento\Framework\DataObject') + ->disableOriginalConstructor() + ->setMethods(['getCarrierCode']) + ->getMock(); + $shippingMethod->expects(static::once()) + ->method('getCarrierCode') + ->willReturn('usps'); + + $order->expects(static::exactly(2)) + ->method('getShippingMethod') + ->with(true) + ->willReturn($shippingMethod); + + $address = $this->getRecipientAddress(); + + $order->expects(static::once()) + ->method('getShippingAddress') + ->willReturn($address); + $order->expects(static::once()) + ->method('getWeight') + ->willReturn(2); + + $storeId = 33; + $shipment = $this->getMockBuilder('Magento\Sales\Model\Order\Shipment') + ->disableOriginalConstructor() + ->getMock(); + $shipment->expects(static::once())->method('getOrder')->willReturn($order); + $shipment->expects(static::once())->method('getStoreId')->willReturn($storeId); + $shipment->expects(static::once())->method('getPackages')->willReturn(''); + + $this->scopeConfig->expects(static::any()) + ->method('getValue') + ->willReturnMap([ + [Shipment::XML_PATH_STORE_REGION_ID, ScopeInterface::SCOPE_STORE, $storeId, 'CA'], + [Shipment::XML_PATH_STORE_ADDRESS1, ScopeInterface::SCOPE_STORE, $storeId, 'Beverly Heals'], + ['general/store_information', ScopeInterface::SCOPE_STORE, $storeId, [ + 'name' => 'General Store', 'phone' => '(244)1500301' + ]], + [Shipment::XML_PATH_STORE_CITY, ScopeInterface::SCOPE_STORE, $storeId, 'LA'], + [Shipment::XML_PATH_STORE_ZIP, ScopeInterface::SCOPE_STORE, $storeId, '90304'], + [Shipment::XML_PATH_STORE_COUNTRY_ID, ScopeInterface::SCOPE_STORE, $storeId, 'US'], + [Shipment::XML_PATH_STORE_ADDRESS2, ScopeInterface::SCOPE_STORE, $storeId, '1st Park Avenue'], + ]); + + $this->region->expects(static::once()) + ->method('load') + ->willReturnSelf(); + $this->region->expects(static::once()) + ->method('getCode') + ->willReturn('CO'); + + $this->labels->requestToShipment($shipment); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function getAuthSession() + { + $user = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getFirstname', 'getLastname', 'getEmail', 'getName']) + ->getMock(); + $user->expects(static::exactly(2)) + ->method('getFirstname') + ->willReturn('John'); + $user->expects(static::exactly(2)) + ->method('getLastname') + ->willReturn('Doe'); + $user->expects(static::once()) + ->method('getName') + ->willReturn('John Doe'); + $user->expects(static::once()) + ->method('getEmail') + ->willReturn('admin@admin.test.com'); + + $authSession = $this->getMockBuilder('Magento\Backend\Model\Auth\Session') + ->disableOriginalConstructor() + ->setMethods(['getUser']) + ->getMock(); + $authSession->expects(static::any())->method('getUser')->willReturn($user); + return $authSession; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function getStoreManager() + { + $store = $this->getMockBuilder('Magento\Store\Model\Store') + ->disableOriginalConstructor() + ->getMock(); + $store->expects(static::any()) + ->method('getBaseCurrencyCode') + ->willReturn('USD'); + + $storeManager = $this->getMockBuilder('Magento\Store\Model\StoreManager') + ->disableOriginalConstructor() + ->setMethods(['getStore']) + ->getMock(); + $storeManager->expects(static::any())->method('getStore')->willReturn($store); + return $storeManager; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function getRegionFactory() + { + $this->region = $this->getMockBuilder('Magento\Directory\Model\Region') + ->disableOriginalConstructor() + ->setMethods(['load', 'getCode']) + ->getMock(); + $regionFactory = $this->getMockBuilder('Magento\Directory\Model\RegionFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $regionFactory->expects(static::any())->method('create')->willReturn($this->region); + return $regionFactory; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function getRecipientAddress() + { + $address = $this->getMockBuilder('Magento\Sales\Model\Order\Address') + ->disableOriginalConstructor() + ->getMock(); + $address->expects(static::once()) + ->method('getRegionId') + ->willReturn(23); + $address->expects(static::exactly(2)) + ->method('getFirstname') + ->willReturn('Chimi'); + $address->expects(static::exactly(2)) + ->method('getLastname') + ->willReturn('Chung'); + $address->expects(static::once()) + ->method('getCompany') + ->willReturn('Software LLC'); + $address->expects(static::once()) + ->method('getTelephone') + ->willReturn('(231) 324-123-31'); + $address->expects(static::once()) + ->method('getEmail') + ->willReturn('chimi.chung@test.com'); + $address->expects(static::exactly(4)) + ->method('getStreetLine') + ->willReturn('66 Pearl St'); + $address->expects(static::once()) + ->method('getCity') + ->willReturn('Denver'); + $address->expects(static::once()) + ->method('getPostcode') + ->willReturn('80203'); + $address->expects(static::once()) + ->method('getCountryId') + ->willReturn(1); + return $address; + } +} diff --git a/app/code/Magento/Shipping/composer.json b/app/code/Magento/Shipping/composer.json index 3083b874f11286fd58eb042c04d7a11562b4254e..521db1f364afe38b8939941cd59141ab4674b312 100644 --- a/app/code/Magento/Shipping/composer.json +++ b/app/code/Magento/Shipping/composer.json @@ -16,6 +16,7 @@ "magento/module-quote": "1.0.0-beta", "magento/module-ui": "1.0.0-beta", "magento/framework": "1.0.0-beta", + "magento/module-user": "1.0.0-beta", "ext-gd": "*", "magento/magento-composer-installer": "*" }, diff --git a/app/code/Magento/Shipping/etc/module.xml b/app/code/Magento/Shipping/etc/module.xml index 6fe825e8deeb3cf3ffb688b8ae9977e041649243..3d9bf51a6b6b17aee455d4314a50e3f65e5b9e23 100644 --- a/app/code/Magento/Shipping/etc/module.xml +++ b/app/code/Magento/Shipping/etc/module.xml @@ -11,6 +11,7 @@ <module name="Magento_Store"/> <module name="Magento_Catalog"/> <module name="Magento_Ui"/> + <module name="Magento_User"/> </sequence> </module> </config> diff --git a/app/code/Magento/Sitemap/Model/Resource/Catalog/Product.php b/app/code/Magento/Sitemap/Model/Resource/Catalog/Product.php index db92eb293b790bd5add1b5b690ca4a0b3e8cfb36..ad5ef50ab3c7a774a6cbdf004b4db748a48fb21d 100644 --- a/app/code/Magento/Sitemap/Model/Resource/Catalog/Product.php +++ b/app/code/Magento/Sitemap/Model/Resource/Catalog/Product.php @@ -391,7 +391,10 @@ class Product extends \Magento\Framework\Model\Resource\Db\AbstractDb protected function _getAllProductImages($product, $storeId) { $product->setStoreId($storeId); - $gallery = $this->_mediaAttribute->loadGallery($product, $this->_getMediaGalleryModel()); + $gallery = $this->_mediaAttribute->loadProductGalleryByAttributeId( + $product, + $this->_getMediaGalleryModel()->getAttribute()->getId() + ); $imagesCollection = []; if ($gallery) { diff --git a/app/code/Magento/Store/App/Action/Plugin/Context.php b/app/code/Magento/Store/App/Action/Plugin/Context.php index 502f5dfa72959882ab6ef2f759ccf2cd854d15db..6e3e3f4e4f5510df99451c684e5fb0358c2ea0b1 100644 --- a/app/code/Magento/Store/App/Action/Plugin/Context.php +++ b/app/code/Magento/Store/App/Action/Plugin/Context.php @@ -74,21 +74,27 @@ class Context \Closure $proceed, \Magento\Framework\App\RequestInterface $request ) { + /** @var \Magento\Store\Model\Store $defaultStore */ $defaultStore = $this->storeManager->getWebsite()->getDefaultStore(); - $this->httpContext->setValue( - HttpContext::CONTEXT_CURRENCY, - $this->session->getCurrencyCode(), - $defaultStore->getDefaultCurrency()->getCode() + + $requestedStoreCode = $this->httpRequest->getParam( + StoreResolverInterface::PARAM_NAME, + $this->storeCookieManager->getStoreCodeFromCookie() ); + /** @var \Magento\Store\Model\Store $currentStore */ + $currentStore = $requestedStoreCode ? $this->storeManager->getStore($requestedStoreCode) : $defaultStore; $this->httpContext->setValue( StoreManagerInterface::CONTEXT_STORE, - $this->httpRequest->getParam( - StoreResolverInterface::PARAM_NAME, - $this->storeCookieManager->getStoreCodeFromCookie() - ) ?: $defaultStore->getCode(), + $currentStore->getCode(), $this->storeManager->getDefaultStoreView()->getCode() ); + + $this->httpContext->setValue( + HttpContext::CONTEXT_CURRENCY, + $this->session->getCurrencyCode() ?: $currentStore->getDefaultCurrencyCode(), + $defaultStore->getDefaultCurrencyCode() + ); return $proceed($request); } } diff --git a/app/code/Magento/Store/App/Response/Redirect.php b/app/code/Magento/Store/App/Response/Redirect.php index 96e46e47432a126dc78b8af324733ebe16dbc64e..1b041e6f05e487ad93c88c6d95da8c9ce1bf98cf 100644 --- a/app/code/Magento/Store/App/Response/Redirect.php +++ b/app/code/Magento/Store/App/Response/Redirect.php @@ -7,6 +7,8 @@ */ namespace Magento\Store\App\Response; +use Magento\Store\Api\StoreResolverInterface; + class Redirect implements \Magento\Framework\App\Response\RedirectInterface { /** @@ -94,6 +96,8 @@ class Redirect implements \Magento\Framework\App\Response\RedirectInterface if (!$this->_isUrlInternal($refererUrl)) { $refererUrl = $this->_storeManager->getStore()->getBaseUrl(); + } else { + $refererUrl = $this->normalizeRefererUrl($refererUrl); } return $refererUrl; } @@ -210,4 +214,57 @@ class Redirect implements \Magento\Framework\App\Response\RedirectInterface } return false; } + + /** + * Normalize path to avoid wrong store change + * + * @param string $refererUrl + * @return string + */ + protected function normalizeRefererUrl($refererUrl) + { + if (!$refererUrl || !filter_var($refererUrl, FILTER_VALIDATE_URL)) { + return $refererUrl; + } + + $redirectParsedUrl = parse_url($refererUrl); + $refererQuery = []; + + if (!isset($redirectParsedUrl['query'])) { + return $refererUrl; + } + + parse_str($redirectParsedUrl['query'], $refererQuery); + + $refererQuery = $this->normalizeRefererQueryParts($refererQuery); + $normalizedUrl = $redirectParsedUrl['scheme'] + . '://' + . $redirectParsedUrl['host'] + . (isset($redirectParsedUrl['port']) ? ':' . $redirectParsedUrl['port'] : '') + . $redirectParsedUrl['path'] + . ($refererQuery ? '?' . http_build_query($refererQuery) : ''); + + return $normalizedUrl; + } + + /** + * Normalize special parts of referer query + * + * @param array $refererQuery + * @return array + */ + protected function normalizeRefererQueryParts($refererQuery) + { + $store = $this->_storeManager->getStore(); + + if ( + $store + && !empty($refererQuery[StoreResolverInterface::PARAM_NAME]) + && ($refererQuery[StoreResolverInterface::PARAM_NAME] !== $store->getCode()) + ) { + $refererQuery[StoreResolverInterface::PARAM_NAME] = $store->getCode(); + } + + return $refererQuery; + } } diff --git a/app/code/Magento/Store/Controller/Store/SwitchAction.php b/app/code/Magento/Store/Controller/Store/SwitchAction.php index 9fa05297ad15e5abbd890e63fd319acadcb0ddfd..647b2cf6c320f7e6f277fd155d9bf52232a450cf 100644 --- a/app/code/Magento/Store/Controller/Store/SwitchAction.php +++ b/app/code/Magento/Store/Controller/Store/SwitchAction.php @@ -14,7 +14,11 @@ use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\Store; use Magento\Store\Model\StoreIsInactiveException; use Magento\Store\Model\StoreResolver; +use Magento\Store\Model\StoreManagerInterface; +/** + * Switch current store view. + */ class SwitchAction extends Action { /** @@ -33,21 +37,31 @@ class SwitchAction extends Action protected $storeRepository; /** + * @var StoreManagerInterface + */ + protected $storeManager; + + /** + * Initialize dependencies. + * * @param ActionContext $context * @param StoreCookieManagerInterface $storeCookieManager * @param HttpContext $httpContext * @param StoreRepositoryInterface $storeRepository + * @param StoreManagerInterface $storeManager */ public function __construct( ActionContext $context, StoreCookieManagerInterface $storeCookieManager, HttpContext $httpContext, - StoreRepositoryInterface $storeRepository + StoreRepositoryInterface $storeRepository, + StoreManagerInterface $storeManager ) { parent::__construct($context); $this->storeCookieManager = $storeCookieManager; $this->httpContext = $httpContext; $this->storeRepository = $storeRepository; + $this->storeManager = $storeManager; } /** @@ -64,7 +78,7 @@ class SwitchAction extends Action $store = $this->storeRepository->getActiveStoreByCode($storeCode); } catch (StoreIsInactiveException $e) { $error = __('Requested store is inactive'); - } catch (\InvalidArgumentException $e) { // TODO: MAGETWO-39826 Need to replace on NoSuchEntityException + } catch (\InvalidArgumentException $e) { $error = __('Requested store is not found'); } @@ -74,11 +88,12 @@ class SwitchAction extends Action return; } - if ($store->getWebsite()->getDefaultStore()->getId() == $store->getId()) { + $defaultStoreView = $this->storeManager->getDefaultStoreView(); + if ($defaultStoreView->getId() == $store->getId()) { $this->storeCookieManager->deleteStoreCookie($store); } else { + $this->httpContext->setValue(Store::ENTITY, $store->getCode(), $defaultStoreView->getCode()); $this->storeCookieManager->setStoreCookie($store); - $this->httpContext->setValue(Store::ENTITY, $store->getCode(), Store::DEFAULT_CODE); } $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); diff --git a/app/code/Magento/Store/Model/Plugin/StoreCookie.php b/app/code/Magento/Store/Model/Plugin/StoreCookie.php new file mode 100644 index 0000000000000000000000000000000000000000..cb4ec64e03a2f76f7a7414d6045b86a32a996426 --- /dev/null +++ b/app/code/Magento/Store/Model/Plugin/StoreCookie.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Store\Model\Plugin; + +use Magento\Store\Api\StoreCookieManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Model\StoreIsInactiveException; +use Magento\Framework\Exception\NoSuchEntityException; +use \InvalidArgumentException; + +/** + * Class StoreCookie + */ +class StoreCookie +{ + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + protected $storeManager; + + /** + * @var StoreCookieManagerInterface + */ + protected $storeCookieManager; + + /** + * @var StoreRepositoryInterface + */ + protected $storeRepository; + + /** + * @param StoreManagerInterface $storeManager + * @param StoreCookieManagerInterface $storeCookieManager + * @param StoreRepositoryInterface $storeRepository + */ + public function __construct( + StoreManagerInterface $storeManager, + StoreCookieManagerInterface $storeCookieManager, + StoreRepositoryInterface $storeRepository + ) { + $this->storeManager = $storeManager; + $this->storeCookieManager = $storeCookieManager; + $this->storeRepository = $storeRepository; + } + + /** + * Delete cookie "store" if the store (a value in the cookie) does not exist or is inactive + * + * @param \Magento\Framework\App\FrontController $subject + * @param callable $proceed + * @param \Magento\Framework\App\RequestInterface $request + * @return mixed + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundDispatch( + \Magento\Framework\App\FrontController $subject, + \Closure $proceed, + \Magento\Framework\App\RequestInterface $request + ) { + $defaultStore = $this->storeManager->getDefaultStoreView(); + $storeCodeFromCookie = $this->storeCookieManager->getStoreCodeFromCookie(); + if ($storeCodeFromCookie) { + try { + $this->storeRepository->getActiveStoreByCode($storeCodeFromCookie); + } catch (StoreIsInactiveException $e) { + $this->storeCookieManager->deleteStoreCookie($defaultStore); + } catch (NoSuchEntityException $e) { + $this->storeCookieManager->deleteStoreCookie($defaultStore); + } catch (InvalidArgumentException $e) { + $this->storeCookieManager->deleteStoreCookie($defaultStore); + } + } + return $proceed($request); + } +} diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index 87217177ae1e55dd01897645c4dfad45074767a4..81070d008e7761bf0bf25b31b43a8a9fd11da7f6 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -90,15 +90,8 @@ class Store extends AbstractExtensibleModel implements /**#@-*/ - /**#@+ - * Code constants - */ - const DEFAULT_CODE = 'default'; - const ADMIN_CODE = 'admin'; - /**#@-*/ - /** * Cache tag */ diff --git a/app/code/Magento/Store/Model/StoreResolver.php b/app/code/Magento/Store/Model/StoreResolver.php index e741318996e07ece10df2988c6478656f1ad9ae8..79a6e53423c79fc953185864180f8fcbae4a41f7 100644 --- a/app/code/Magento/Store/Model/StoreResolver.php +++ b/app/code/Magento/Store/Model/StoreResolver.php @@ -81,7 +81,11 @@ class StoreResolver implements \Magento\Store\Api\StoreResolverInterface $storeCode = $this->request->getParam(self::PARAM_NAME, $this->storeCookieManager->getStoreCodeFromCookie()); if ($storeCode) { - $store = $this->getRequestedStoreByCode($storeCode); + try { + $store = $this->getRequestedStoreByCode($storeCode); + } catch (NoSuchEntityException $e) { + $store = $this->getDefaultStoreById($defaultStoreId); + } if (!in_array($store->getId(), $stores)) { throw new NoSuchEntityException(__('Requested scope cannot be loaded')); diff --git a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php index 61a380380ed4deda3ee77021219495444e21c733..e6c679b2dd4d28fa1157b83529ff7a7751012e4a 100644 --- a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php +++ b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php @@ -19,6 +19,10 @@ use Magento\Store\Model\StoreManagerInterface; */ class ContextTest extends \PHPUnit_Framework_TestCase { + const CURRENCY_SESSION = 'CNY'; + const CURRENCY_DEFAULT = 'USD'; + const CURRENCY_CURRENT_STORE = 'UAH'; + /** * @var \Magento\Store\App\Action\Plugin\Context */ @@ -55,9 +59,9 @@ class ContextTest extends \PHPUnit_Framework_TestCase protected $storeMock; /** - * @var \Magento\Directory\Model\Currency|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject */ - protected $currencyMock; + protected $currentStoreMock; /** * @var \Magento\Store\Model\Website|\PHPUnit_Framework_MockObject_MockObject @@ -114,9 +118,9 @@ class ContextTest extends \PHPUnit_Framework_TestCase '', false ); - $this->currencyMock = $this->getMock( - 'Magento\Directory\Model\Currency', - ['getCode', '__wakeup'], + $this->currentStoreMock = $this->getMock( + 'Magento\Store\Model\Store', + [], [], '', false @@ -150,33 +154,25 @@ class ContextTest extends \PHPUnit_Framework_TestCase 'storeCookieManager' => $this->storeCookieManager, ] ); - } - - /** - * Test aroundDispatch - */ - public function testAroundDispatch() - { $this->storeManager->expects($this->once()) ->method('getWebsite') ->will($this->returnValue($this->websiteMock)); $this->storeManager->method('getDefaultStoreView') ->willReturn($this->storeMock); + $this->storeManager->method('getStore') + ->willReturn($this->currentStoreMock); $this->websiteMock->expects($this->once()) ->method('getDefaultStore') ->will($this->returnValue($this->storeMock)); $this->storeMock->expects($this->once()) - ->method('getDefaultCurrency') - ->will($this->returnValue($this->currencyMock)); + ->method('getDefaultCurrencyCode') + ->will($this->returnValue(self::CURRENCY_DEFAULT)); $this->storeMock->expects($this->once()) ->method('getCode') ->willReturn('default'); - $this->currencyMock->expects($this->once()) + $this->currentStoreMock->expects($this->once()) ->method('getCode') - ->will($this->returnValue('UAH')); - $this->sessionMock->expects($this->once()) - ->method('getCurrencyCode') - ->will($this->returnValue('UAH')); + ->willReturn('custom_store'); $this->storeCookieManager->expects($this->once()) ->method('getStoreCodeFromCookie') ->will($this->returnValue('storeCookie')); @@ -184,16 +180,41 @@ class ContextTest extends \PHPUnit_Framework_TestCase ->method('getParam') ->with($this->equalTo('___store')) ->will($this->returnValue('default')); - $this->httpContextMock->expects($this->atLeastOnce()) + $this->currentStoreMock->expects($this->any()) + ->method('getDefaultCurrencyCode') + ->will($this->returnValue(self::CURRENCY_CURRENT_STORE)); + } + + public function testAroundDispatchCurrencyFromSession() + { + $this->sessionMock->expects($this->any()) + ->method('getCurrencyCode') + ->will($this->returnValue(self::CURRENCY_SESSION)); + + $this->httpContextMock->expects($this->at(0)) + ->method('setValue') + ->with(StoreManagerInterface::CONTEXT_STORE, 'custom_store', 'default'); + /** Make sure that current currency is taken from session if available */ + $this->httpContextMock->expects($this->at(1)) ->method('setValue') - ->will( - $this->returnValueMap( - [ - [Context::CONTEXT_CURRENCY, 'UAH', 'UAH', $this->httpContextMock], - [StoreManagerInterface::CONTEXT_STORE, 'default', 'default', $this->httpContextMock], - ] - ) - ); + ->with(Context::CONTEXT_CURRENCY, self::CURRENCY_SESSION, self::CURRENCY_DEFAULT); + + $this->assertEquals( + 'ExpectedValue', + $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) + ); + } + + public function testDispatchCurrentStoreCurrency() + { + $this->httpContextMock->expects($this->at(0)) + ->method('setValue') + ->with(StoreManagerInterface::CONTEXT_STORE, 'custom_store', 'default'); + /** Make sure that current currency is taken from current store if no value is provided in session */ + $this->httpContextMock->expects($this->at(1)) + ->method('setValue') + ->with(Context::CONTEXT_CURRENCY, self::CURRENCY_CURRENT_STORE, self::CURRENCY_DEFAULT); + $this->assertEquals( 'ExpectedValue', $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) diff --git a/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php b/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3b2197494bef8f3406b0e8dbab1da3777db13a2b --- /dev/null +++ b/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Store\Test\Unit\Model\Plugin; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreIsInactiveException; +use \InvalidArgumentException; + +/** + * Class StoreCookieTest + */ +class StoreCookieTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Store\Model\Plugin\StoreCookie + */ + protected $plugin; + + /** + * @var \Magento\Store\Model\StoreManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeManagerMock; + + /** + * @var \Magento\Store\Api\StoreCookieManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeCookieManagerMock; + + /** + * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeMock; + + /** + * @var \Closure + */ + protected $closureMock; + + /** + * @var \Magento\Framework\App\FrontController|\PHPUnit_Framework_MockObject_MockObject + */ + protected $subjectMock; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $requestMock; + + /** + * @var \Magento\Store\Api\StoreRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeRepositoryMock; + + /** + * Set up + */ + public function setUp() + { + $this->storeManagerMock = $this->getMockBuilder('Magento\Store\Model\StoreManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->storeCookieManagerMock = $this->getMockBuilder('Magento\Store\Api\StoreCookieManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->storeMock = $this->getMockBuilder('Magento\Store\Model\Store') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->closureMock = function () { + return 'ExpectedValue'; + }; + + $this->subjectMock = $this->getMockBuilder('Magento\Framework\App\FrontController') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->requestMock = $this->getMockBuilder('Magento\Framework\App\RequestInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->storeRepositoryMock = $this->getMockBuilder('Magento\Store\Api\StoreRepositoryInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->plugin = (new ObjectManager($this))->getObject( + 'Magento\Store\Model\Plugin\StoreCookie', + [ + 'storeManager' => $this->storeManagerMock, + 'storeCookieManager' => $this->storeCookieManagerMock, + 'storeRepository' => $this->storeRepositoryMock + ] + ); + } + + public function testAroundDispatchNoSuchEntity() + { + $storeCode = 'store'; + $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); + $this->storeCookieManagerMock->expects($this->once())->method('getStoreCodeFromCookie')->willReturn($storeCode); + $this->storeRepositoryMock->expects($this->once()) + ->method('getActiveStoreByCode') + ->willThrowException(new NoSuchEntityException); + $this->storeCookieManagerMock->expects($this->once())->method('deleteStoreCookie')->with($this->storeMock); + $this->assertEquals( + 'ExpectedValue', + $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) + ); + } + + public function testAroundDispatchStoreIsInactive() + { + $storeCode = 'store'; + $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); + $this->storeCookieManagerMock->expects($this->once())->method('getStoreCodeFromCookie')->willReturn($storeCode); + $this->storeRepositoryMock->expects($this->once()) + ->method('getActiveStoreByCode') + ->willThrowException(new StoreIsInactiveException); + $this->storeCookieManagerMock->expects($this->once())->method('deleteStoreCookie')->with($this->storeMock); + $this->assertEquals( + 'ExpectedValue', + $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) + ); + } + + public function testAroundDispatchInvalidArgument() + { + $storeCode = 'store'; + $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); + $this->storeCookieManagerMock->expects($this->once())->method('getStoreCodeFromCookie')->willReturn($storeCode); + $this->storeRepositoryMock->expects($this->once()) + ->method('getActiveStoreByCode') + ->willThrowException(new InvalidArgumentException); + $this->storeCookieManagerMock->expects($this->once())->method('deleteStoreCookie')->with($this->storeMock); + $this->assertEquals( + 'ExpectedValue', + $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) + ); + } + + public function testAroundDispatchNoStoreCookie() + { + $storeCode = null; + $this->storeManagerMock->expects($this->once())->method('getDefaultStoreView')->willReturn($this->storeMock); + $this->storeCookieManagerMock->expects($this->once())->method('getStoreCodeFromCookie')->willReturn($storeCode); + $this->storeRepositoryMock->expects($this->never())->method('getActiveStoreByCode'); + $this->storeCookieManagerMock->expects($this->never())->method('deleteStoreCookie')->with($this->storeMock); + $this->assertEquals( + 'ExpectedValue', + $this->plugin->aroundDispatch($this->subjectMock, $this->closureMock, $this->requestMock) + ); + } +} diff --git a/app/code/Magento/Store/etc/cache.xml b/app/code/Magento/Store/etc/cache.xml index 6dae6b683b09121e6bc3820d0d797fda2d7877bc..715a5a12e55b2534294eb688907d3522cbfa5e5e 100644 --- a/app/code/Magento/Store/etc/cache.xml +++ b/app/code/Magento/Store/etc/cache.xml @@ -22,6 +22,10 @@ <label>Collections Data</label> <description>Collection data files.</description> </type> + <type name="reflection" translate="label,description" instance="Magento\Framework\App\Cache\Type\Reflection"> + <label>Reflection Data</label> + <description>API interfaces reflection data.</description> + </type> <type name="db_ddl" translate="label,description" instance="Magento\Framework\DB\Adapter\DdlCache"> <label>Database DDL operations</label> <description>Results of DDL queries, such as describing tables or indexes.</description> diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index c8bc755197c49bb0f7aa5e0351784fe7a8b04ef9..778ff46a82b3e767e207ec4713b0feff2a41bc82 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -225,6 +225,11 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Reflection\MethodsMap"> + <arguments> + <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Reflection</argument> + </arguments> + </type> <type name="Magento\Framework\Url"> <arguments> <argument name="scopeType" xsi:type="const">Magento\Store\Model\ScopeInterface::SCOPE_STORE</argument> @@ -272,6 +277,7 @@ </type> <type name="Magento\Framework\App\FrontController"> <plugin name="install" type="Magento\Framework\Module\Plugin\DbStatusValidator" sortOrder="40"/> + <plugin name="storeCookieValidate" type="Magento\Store\Model\Plugin\StoreCookie" sortOrder="10"/> </type> <type name="Magento\Framework\Module\Plugin\DbStatusValidator"> <arguments> diff --git a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml index a43a5c7885a96a19534c859bf0051cb225044c18..8ce4c95f94275746b0df6182cbf15052c4ff1934 100644 --- a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml +++ b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml @@ -37,9 +37,8 @@ </head> <body> <!--Remove Magento page content--> - <remove name="page.wrapper"/> - <remove name="after.body.start"/> - <remove name="page.bottom"/> + <referenceContainer name="page.wrapper" remove="true"/> + <referenceBlock name="requirejs-config" remove="true"/> <referenceContainer name="root"> <block name="swaggerUiContent" class="Magento\Framework\View\Element\Template" template="Magento_Swagger::swagger-ui/index.phtml"/> </referenceContainer> diff --git a/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml b/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml index b9c6e924b3dc9575ea6ed19f867810402f6bf4bc..16f25fd80861cd9e2918969aaa9036507a36de22 100644 --- a/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml +++ b/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml @@ -18,7 +18,7 @@ $schemaUrl = rtrim($block->getBaseUrl(), '/') . '/rest/default/schema?services=a ?> <!DOCTYPE html> -<script language="text/javascript"> +<script> $(function () { var url = window.location.search.match(/url=([^&]+)/); if (url && url.length > 1) { diff --git a/app/code/Magento/Swatches/Setup/InstallData.php b/app/code/Magento/Swatches/Setup/InstallData.php index 63774d2634c14e76ea95c315cc818eb78003a478..183c80af48b9604202d3760bece8ae4d87a72410 100644 --- a/app/code/Magento/Swatches/Setup/InstallData.php +++ b/app/code/Magento/Swatches/Setup/InstallData.php @@ -10,6 +10,7 @@ use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Eav\Setup\EavSetupFactory; +use Magento\Eav\Setup\EavSetup; /** * Class InstallData @@ -60,8 +61,7 @@ class InstallData implements InstallDataInterface 'required' => false, 'sort_order' => 3, 'global' => \Magento\Catalog\Model\Resource\Eav\Attribute::SCOPE_STORE, - 'used_in_product_listing' => true, - 'group' => 'Images', + 'used_in_product_listing' => true ] ); } diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index da38b6169f45d180cc72f4716ee5eae31b51a5d3..6cc94c567d3e8195cf6e7ded5d1d162b3fc193dc 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -11,11 +11,13 @@ "magento/module-backend": "1.0.0-beta", "magento/module-media-storage": "1.0.0-beta", "magento/module-config": "1.0.0-beta", - "magento/module-layered-navigation": "1.0.0-beta", "magento/module-theme": "1.0.0-beta", "magento/framework": "1.0.0-beta", "magento/magento-composer-installer": "*" }, + "suggest": { + "magento/module-layered-navigation": "1.0.0-beta" + }, "type": "magento2-module", "version": "1.0.0-beta", "license": [ diff --git a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml index b1951c3f2ae9dfeb30ea959035a7f66ef648d76e..8b2b70a2cdbfb947c6f7716c1d51d23d07deb0ee 100644 --- a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml +++ b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml @@ -8,7 +8,7 @@ ?> <script> -require(["jquery", "collapsable", "prototype"], function(jQuery){ +require(["jquery", 'uiRegistry', "collapsable", "prototype"], function(jQuery, rg){ function toggleApplyVisibility(select) { if ($(select).value == 1) { @@ -38,6 +38,9 @@ function checkOptionsPanelVisibility(){ if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect')){ panel.show(); + rg.get('manage-options-panel', function() { + jQuery('#manage-options-panel').trigger('render'); + }); } else { panel.hide(); @@ -48,6 +51,9 @@ function checkOptionsPanelVisibility(){ if($('frontend_input') && $('frontend_input').value=='swatch_visual') { panel.show(); + rg.get('swatch-visual-options-panel', function() { + jQuery('#swatch-visual-options-panel').trigger('render'); + }); } else { panel.hide(); @@ -58,6 +64,9 @@ function checkOptionsPanelVisibility(){ if($('frontend_input') && $('frontend_input').value=='swatch_text') { panel.show(); + rg.get('swatch-text-options-panel', function() { + jQuery('#swatch-text-options-panel').trigger('render'); + }); } else { panel.hide(); diff --git a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml index e521b1e2603827fe189e0051f995566c9a68393a..63af3ebae14bcd7e0610c47b1485aaecd5f5b19f 100644 --- a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml +++ b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml @@ -8,7 +8,7 @@ /** @var $block \Magento\Swatches\Block\Adminhtml\Attribute\Edit\Options\Text */ ?> -<fieldset class="fieldset"> +<fieldset class="fieldset ignore-validate"> <legend class="legend"><span><?php /* @escapeNotVerified */ echo __('Manage Swatch (values of your attribute)') ?></span></legend> <div id="swatch-text-options-panel"> <?php //@todo move style to css file ?> @@ -78,128 +78,21 @@ </td> </tr> </script> - <script> - require([ - 'jquery', - 'mage/template', - 'jquery/ui', - 'prototype' - ], function(jQuery, mageTemplate){ - - var swatchOptionTextDefaultInputType = 'radio'; - - var swatchTextOption = { - table: $('swatch-text-options-table'), - itemCount: 0, - totalItems: 0, - isReadOnly: <?php echo (int)$block->getReadOnly(); ?>, - add: function(data) { - this.template = mageTemplate('#swatch-text-row-template'); - - var isNewOption = false; - if (typeof data.id == 'undefined') { - data = { - 'id': 'option_' + this.itemCount, - 'sort_order': this.itemCount + 1 - }; - isNewOption = true; - } - if (!data.intype) { - data.intype = swatchOptionTextDefaultInputType; - } - - Element.insert($$('[data-role=swatch-text-options-container]')[0], this.template({data: data})); - visibleRadio = $$('#swatch-text-options-panel [name="defaulttext[]"]').findAll(function(el) { return el.up().up().visible(); }); - if (visibleRadio.length == 1 && isNewOption == true) { - visibleRadio[0].checked = true; - } - if (isNewOption && !this.isReadOnly) { - this.enableNewOptionDeleteButton(data.id); - } - this.bindRemoveButtons(); - this.itemCount++; - this.totalItems++; - this.updateItemsCountField(); - }, - remove: function(event) { - var element = $(Event.findElement(event, 'tr')); // !!! Button already have table parent in safari - // Safari workaround - element.ancestors().each(function(parentItem) { - if (parentItem.hasClassName('option-row')) { - element = parentItem; - throw $break; - } else if (parentItem.hasClassName('box')) { - throw $break; - } - }); - - if (element) { - var elementFlags = element.getElementsByClassName('delete-flag'); - if (elementFlags[0]) { - elementFlags[0].value = 1; - } - - element.addClassName('no-display'); - element.addClassName('template'); - element.hide(); - this.totalItems--; - this.updateItemsCountField(); - } - }, - updateItemsCountField: function() { - $('swatch-text-option-count-check').value = this.totalItems > 0 ? '1' : ''; - }, - enableNewOptionDeleteButton: function(id) { - $$('#delete_button_swatch_container_' + id + ' button').each(function(button) { - button.enable(); - button.removeClassName('disabled'); - }); - }, - bindRemoveButtons: function() { - var buttons = $$('.delete-option'); - for (var i = 0; i < buttons.length; i++) { - if (!$(buttons[i]).binded) { - $(buttons[i]).binded = true; - Event.observe(buttons[i], 'click', this.remove.bind(this)); - } - } + <?php + $values = []; + foreach($block->getOptionValues() as $value) { + array_push($values, $value->getData()); + } + ?> + <script type="text/x-magento-init"> + { + "*": { + "Magento_Swatches/js/text": { + "attributesData": <?php /* @escapeNotVerified */ echo json_encode($values); ?>, + "isSortable": <?php echo (int)(!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) ?>, + "isReadOnly": <?php echo (int)$block->getReadOnly(); ?> } - }; - - swatchTextOption.bindRemoveButtons(); - - if ($('add_new_swatch_text_option_button')) { - Event.observe('add_new_swatch_text_option_button', 'click', swatchTextOption.add.bind(swatchTextOption)); } - <?php foreach ($block->getOptionValues() as $_value): ?> - swatchTextOption.add(<?php /* @escapeNotVerified */ echo $_value->toJson() ?>); - <?php endforeach; ?> - <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?> - jQuery(function($) { - $('[data-role=swatch-text-options-container]').sortable({ - distance: 8, - tolerance: "pointer", - cancel: 'input, button', - axis: 'y', - update: function() { - $('[data-role=swatch-text-options-container] [data-role=order]').each(function(index, element) { - $(element).val(index + 1); - }); - } - }); - }); - <?php endif; ?> - - jQuery(document).ready(function(){ - if (jQuery('#frontend_input').val() != 'swatch_text') { - jQuery('.swatch-text-field-0').removeClass('required-option'); - } - }); - - window.swatchTextOption = swatchTextOption; - window.swatchOptionTextDefaultInputType = swatchOptionTextDefaultInputType; - //]]> - - }); + } </script> </fieldset> \ No newline at end of file diff --git a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/visual.phtml b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/visual.phtml index 1098414c9cdd3d9cbcef5175cc811b791f74f9bb..a236cf00dd1ab38353d560feb986ed05001f0a96 100644 --- a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/visual.phtml +++ b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/visual.phtml @@ -8,7 +8,7 @@ /** @var $block \Magento\Swatches\Block\Adminhtml\Attribute\Edit\Options\Visual */ ?> -<fieldset class="fieldset"> +<fieldset class="fieldset ignore-validate"> <legend class="legend"><span><?php /* @escapeNotVerified */ echo __('Manage Swatch (values of your attribute)') ?></span></legend> <div id="swatch-visual-options-panel"> <?php //@todo move style to css file ?> @@ -61,7 +61,7 @@ <div id="swatch_container_option_<%- data.id %>" class="swatch_submenu_container" style="display: none;"> <div class="swatch_row" style="border:1px solid black; position: relative;"> - <div id="colorpicker_<%- data.id %>" class="swatch_row_name"> + <div class="swatch_row_name colorpicker_handler"> <p>Choose a color</p> </div> </div> @@ -154,161 +154,21 @@ form.up().up().hide(); } </script> - <script> - require([ - 'jquery', - 'mage/template', - 'jquery/ui', - 'prototype' - ], function(jQuery, mageTemplate){ - - var swatchOptionVisualDefaultInputType = 'radio'; - - jQuery('body').click(function (event) - { - element = jQuery(event.target); - jQuery.each(jQuery('.swatch_submenu_container'), function(key, elem){ - if (element.parents('.swatch_submenu_container').length == 1) { - return false; - } - if (elem.id != element.next().attr('id')) { - elem.hide(); - } - }); - if ( - element.parents('.swatch_submenu_container').length == 1 - || (element.attr('id') && element.attr('id').indexOf('swatch_window_option') >= 0) - ) { - return; - } else { - jQuery('.swatch_submenu_container').hide(); - } - }); - - var swatchVisualOption = { - table: $('swatch-visual-options-table'), - itemCount: 0, - totalItems: 0, - isReadOnly: <?php echo (int)$block->getReadOnly(); ?>, - add: function(data) { - this.template = mageTemplate('#swatch-visual-row-template'); - - var isNewOption = false; - if (typeof data.id == 'undefined') { - data = { - 'id': 'option_' + this.itemCount, - 'sort_order': this.itemCount + 1, - 'empty_class': "unavailable" - }; - isNewOption = true; - } else { - if(data.defaultswatch0 == '') { - data['empty_class'] = "unavailable"; - } - } - if (!data.intype) { - data.intype = swatchOptionVisualDefaultInputType; - } - - Element.insert($$('[data-role=swatch-visual-options-container]')[0], this.template({data: data})); - visibleRadio = $$('#swatch-visual-options-panel [name="defaultvisual[]"]').findAll(function(el) { return el.up().up().visible(); }); - if (visibleRadio.length == 1 && isNewOption == true) { - visibleRadio[0].checked = true; - } - if (isNewOption && !this.isReadOnly) { - this.enableNewOptionDeleteButton(data.id); - } - this.bindRemoveButtons(); - this.itemCount++; - this.totalItems++; - this.updateItemsCountField(); - jQuery('#colorpicker_'+data.id).ColorPicker({ - onShow: function(picker) { - var menu = jQuery('#swatch_container_option_'+data.id); - menu.hide(); - }, - onSubmit: function(hsb, hex, rgb, el) { - jQuery(el).ColorPickerHide(); - var container = jQuery(el).parent().parent().prev(); - container.parent().removeClass("unavailable"); - container.prev('input').val('#'+hex); - container.css('background', '#'+hex); - } - }); - }, - remove: function(event) { - var element = $(Event.findElement(event, 'tr')); // !!! Button already have table parent in safari - // Safari workaround - element.ancestors().each(function(parentItem) { - if (parentItem.hasClassName('option-row')) { - element = parentItem; - throw $break; - } else if (parentItem.hasClassName('box')) { - throw $break; - } - }); - - if (element) { - var elementFlags = element.getElementsByClassName('delete-flag'); - if (elementFlags[0]) { - elementFlags[0].value = 1; - } - - element.addClassName('no-display'); - element.addClassName('template'); - element.hide(); - this.totalItems--; - this.updateItemsCountField(); - } - }, - updateItemsCountField: function() { - $('swatch-visual-option-count-check').value = this.totalItems > 0 ? '1' : ''; - }, - enableNewOptionDeleteButton: function(id) { - $$('#delete_button_swatch_container_' + id + ' button').each(function(button) { - button.enable(); - button.removeClassName('disabled'); - }); - }, - bindRemoveButtons: function() { - var buttons = $$('.delete-option'); - for (var i = 0; i < buttons.length; i++) { - if (!$(buttons[i]).binded) { - $(buttons[i]).binded = true; - Event.observe(buttons[i], 'click', this.remove.bind(this)); - } + <?php + $values = []; + foreach($block->getOptionValues() as $value) { + array_push($values, $value->getData()); + } + ?> + <script type="text/x-magento-init"> + { + "*": { + "Magento_Swatches/js/visual": { + "attributesData": <?php /* @escapeNotVerified */ echo json_encode($values); ?>, + "isSortable": <?php echo (int)(!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()) ?>, + "isReadOnly": <?php echo (int)$block->getReadOnly(); ?> } } - }; - - swatchVisualOption.bindRemoveButtons(); - - if ($('add_new_swatch_visual_option_button')) { - Event.observe('add_new_swatch_visual_option_button', 'click', swatchVisualOption.add.bind(swatchVisualOption)); } - <?php foreach ($block->getOptionValues() as $_value): ?> - swatchVisualOption.add(<?php /* @escapeNotVerified */ echo $_value->toJson() ?>); - <?php endforeach; ?> - <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()):?> - jQuery(function($) { - $('[data-role=swatch-visual-options-container]').sortable({ - distance: 8, - tolerance: "pointer", - cancel: 'input, button', - axis: 'y', - update: function() { - $('[data-role=swatch-visual-options-container] [data-role=order]').each(function(index, element) { - $(element).val(index + 1); - }); - } - }); - }); - <?php endif; ?> - - window.swatchVisualOption = swatchVisualOption; - window.swatchOptionVisualDefaultInputType = swatchOptionVisualDefaultInputType; - //]]> - - }); </script> </fieldset> diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/text.js b/app/code/Magento/Swatches/view/adminhtml/web/js/text.js new file mode 100644 index 0000000000000000000000000000000000000000..7b7e05b7ccebe9a891e18e347b6263acc618abbb --- /dev/null +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/text.js @@ -0,0 +1,187 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable no-undef */ +// jscs:disable jsDoc + +define([ + 'jquery', + 'mage/template', + 'uiRegistry', + 'jquery/ui', + 'prototype' +], function (jQuery, mageTemplate, rg) { + 'use strict'; + + return function (config) { + var swatchOptionTextDefaultInputType = 'radio', + swatchTextOption = { + table: $('swatch-text-options-table'), + itemCount: 0, + totalItems: 0, + rendered: 0, + isReadOnly: config.isReadOnly, + template: mageTemplate('#swatch-text-row-template'), + add: function (data, render) { + var isNewOption = false, + element, visibleRadio; + + if (typeof data.id == 'undefined') { + data = { + 'id': 'option_' + this.itemCount, + 'sort_order': this.itemCount + 1 + }; + isNewOption = true; + } + + if (!data.intype) { + data.intype = swatchOptionTextDefaultInputType; + } + + element = this.template({ + data: data + }); + + if (isNewOption) { + visibleRadio = $$('#swatch-text-options-panel [name="defaulttext[]"]').findAll(function (el) { + return el.up().up().visible(); + }); + + if (visibleRadio.length === 1) { + visibleRadio[0].checked = true; + } + + if (!this.isReadOnly) { + this.enableNewOptionDeleteButton(data.id); + } + } + this.itemCount++; + this.totalItems++; + this.elements += element; + + if (render) { + this.render(); + } + }, + remove: function (event) { + var element = $(Event.findElement(event, 'tr')), + elementFlags; // !!! Button already have table parent in safari + + // Safari workaround + element.ancestors().each(function (parentItem) { + if (parentItem.hasClassName('option-row')) { + element = parentItem; + throw $break; + } else if (parentItem.hasClassName('box')) { + throw $break; + } + }); + + if (element) { + elementFlags = element.getElementsByClassName('delete-flag'); + + if (elementFlags[0]) { + elementFlags[0].value = 1; + } + + element.addClassName('no-display'); + element.addClassName('template'); + element.hide(); + this.totalItems--; + this.updateItemsCountField(); + } + }, + updateItemsCountField: function () { + $('swatch-text-option-count-check').value = this.totalItems > 0 ? '1' : ''; + }, + enableNewOptionDeleteButton: function (id) { + $$('#delete_button_swatch_container_' + id + ' button').each(function (button) { + button.enable(); + button.removeClassName('disabled'); + }); + }, + bindRemoveButtons: function () { + jQuery('#swatch-text-options-panel').on('click', '.delete-option', this.remove.bind(this)); + }, + render: function () { + Element.insert($$('[data-role=swatch-text-options-container]')[0], this.elements); + this.elements = ''; + }, + renderWithDelay: function (data, from, step, delay) { + var arrayLength = data.length, + len; + + for (len = from + step; from < len && from < arrayLength; from++) { + this.add(data[from]); + } + this.render(); + + if (from === arrayLength) { + this.updateItemsCountField(); + this.bindRemoveButtons(); + this.rendered = 1; + jQuery('body').trigger('processStop'); + + return true; + } + setTimeout(this.renderWithDelay.bind(this, data, from, step, delay), delay); + }, + ignoreValidate: function () { + var ignore = '.ignore-validate input, ' + + '.ignore-validate select, ' + + '.ignore-validate textarea'; + + jQuery('#edit_form').data('validator').settings.forceIgnore = ignore; + } + }; + + if ($('add_new_swatch_text_option_button')) { + Event.observe( + 'add_new_swatch_text_option_button', + 'click', + swatchTextOption.add.bind(swatchTextOption, true) + ); + } + jQuery('#swatch-text-options-panel').on('render', function () { + swatchTextOption.ignoreValidate(); + + if (swatchTextOption.rendered) { + return false; + } + jQuery('body').trigger('processStart'); + swatchTextOption.renderWithDelay(config.attributesData, 0, 100, 300); + swatchTextOption.bindRemoveButtons(); + }); + + if (config.isSortable) { + jQuery(function ($) { + $('[data-role=swatch-text-options-container]').sortable({ + distance: 8, + tolerance: 'pointer', + cancel: 'input, button', + axis: 'y', + update: function () { + $('[data-role=swatch-text-options-container] [data-role=order]').each( + function (index, element) { + $(element).val(index + 1); + } + ); + } + }); + }); + } + + jQuery(document).ready(function () { + if (jQuery('#frontend_input').val() !== 'swatch_text') { + jQuery('.swatch-text-field-0').removeClass('required-option'); + } + }); + + window.swatchTextOption = swatchTextOption; + window.swatchOptionTextDefaultInputType = swatchOptionTextDefaultInputType; + + rg.set('swatch-text-options-panel', swatchTextOption); + }; +}); diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js b/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js new file mode 100644 index 0000000000000000000000000000000000000000..49042c621d69461061d2e3608c6aa21682a559f1 --- /dev/null +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/visual.js @@ -0,0 +1,227 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable no-undef */ +// jscs:disable jsDoc + +define([ + 'jquery', + 'mage/template', + 'uiRegistry', + 'prototype', + 'jquery/ui' +], function (jQuery, mageTemplate, rg) { + 'use strict'; + + return function (config) { + var swatchOptionVisualDefaultInputType = 'radio', + swatchVisualOption = { + table: $('swatch-visual-options-table'), + itemCount: 0, + totalItems: 0, + rendered: 0, + isReadOnly: config.isReadOnly, + template: mageTemplate('#swatch-visual-row-template'), + add: function (data, render) { + var isNewOption = false, + element, visibleRadio; + + if (typeof data.id == 'undefined') { + data = { + 'id': 'option_' + this.itemCount, + 'sort_order': this.itemCount + 1, + 'empty_class': 'unavailable' + }; + isNewOption = true; + } else if (data.defaultswatch0 === '') { + data['empty_class'] = 'unavailable'; + } + + if (!data.intype) { + data.intype = swatchOptionVisualDefaultInputType; + } + element = this.template({ + data: data + }); + + if (isNewOption) { + visibleRadio = $$('#swatch-visual-options-panel [name="defaultvisual[]"]').findAll( + function (el) { + return el.up().up().visible(); + } + ); + + if (visibleRadio.length === 1) { + visibleRadio[0].checked = true; + } + + if (!this.isReadOnly) { + this.enableNewOptionDeleteButton(data.id); + } + } + this.itemCount++; + this.totalItems++; + this.elements += element; + + if (render) { + this.render(); + } + }, + initColorPicker: function () { + var element = this, + hiddenColorPicker = !jQuery(element).data('colorpickerId'); + + jQuery(this).ColorPicker({ + onShow: function () { + var color = jQuery(element).parent().parent().prev().prev('input').val(), + menu = jQuery(this).parents('.swatch_submenu_container'); + + menu.hide(); + jQuery(element).ColorPickerSetColor(color); + }, + onSubmit: function (hsb, hex, rgb, el) { + var container = jQuery(el).parent().parent().prev(); + + jQuery(el).ColorPickerHide(); + container.parent().removeClass('unavailable'); + container.prev('input').val('#' + hex); + container.css('background', '#' + hex); + } + }); + + if (hiddenColorPicker) { + jQuery(this).ColorPickerShow(); + } + }, + remove: function (event) { + var element = $(Event.findElement(event, 'tr')), + elementFlags; // !!! Button already have table parent in safari + + // Safari workaround + element.ancestors().each(function (parentItem) { + if (parentItem.hasClassName('option-row')) { + element = parentItem; + throw $break; + } else if (parentItem.hasClassName('box')) { + throw $break; + } + }); + + if (element) { + elementFlags = element.getElementsByClassName('delete-flag'); + + if (elementFlags[0]) { + elementFlags[0].value = 1; + } + + element.addClassName('no-display'); + element.addClassName('template'); + element.hide(); + this.totalItems--; + this.updateItemsCountField(); + } + }, + updateItemsCountField: function () { + $('swatch-visual-option-count-check').value = this.totalItems > 0 ? '1' : ''; + }, + enableNewOptionDeleteButton: function (id) { + $$('#delete_button_swatch_container_' + id + ' button').each(function (button) { + button.enable(); + button.removeClassName('disabled'); + }); + }, + bindRemoveButtons: function () { + jQuery('#swatch-visual-options-panel').on('click', '.delete-option', this.remove.bind(this)); + }, + render: function () { + Element.insert($$('[data-role=swatch-visual-options-container]')[0], this.elements); + this.elements = ''; + }, + renderWithDelay: function (data, from, step, delay) { + var arrayLength = data.length, + len; + + for (len = from + step; from < len && from < arrayLength; from++) { + this.add(data[from]); + } + this.render(); + + if (from === arrayLength) { + this.updateItemsCountField(); + this.rendered = 1; + jQuery('body').trigger('processStop'); + + return true; + } + setTimeout(this.renderWithDelay.bind(this, data, from, step, delay), delay); + }, + ignoreValidate: function () { + var ignore = '.ignore-validate input, ' + + '.ignore-validate select, ' + + '.ignore-validate textarea'; + + jQuery('#edit_form').data('validator').settings.forceIgnore = ignore; + } + }; + + if ($('add_new_swatch_visual_option_button')) { + Event.observe( + 'add_new_swatch_visual_option_button', + 'click', + swatchVisualOption.add.bind(swatchVisualOption, {}, true) + ); + } + + jQuery('#swatch-visual-options-panel').on('render', function () { + swatchVisualOption.ignoreValidate(); + + if (swatchVisualOption.rendered) { + return false; + } + jQuery('body').trigger('processStart'); + swatchVisualOption.renderWithDelay(config.attributesData, 0, 100, 300); + swatchVisualOption.bindRemoveButtons(); + jQuery('#swatch-visual-options-panel').on( + 'click', + '.colorpicker_handler', + swatchVisualOption.initColorPicker + ); + }); + jQuery('body').on('click', function (event) { + var element = jQuery(event.target); + + if ( + element.parents('.swatch_submenu_container').length === 1 || + element.next('div.swatch_submenu_container').length === 1 + ) { + return true; + } + jQuery('.swatch_submenu_container').hide(); + }); + + if (config.isSortable) { + jQuery(function ($) { + $('[data-role=swatch-visual-options-container]').sortable({ + distance: 8, + tolerance: 'pointer', + cancel: 'input, button', + axis: 'y', + update: function () { + $('[data-role=swatch-visual-options-container] [data-role=order]').each( + function (index, element) { + $(element).val(index + 1); + } + ); + } + }); + }); + } + + window.swatchVisualOption = swatchVisualOption; + window.swatchOptionVisualDefaultInputType = swatchOptionVisualDefaultInputType; + + rg.set('swatch-visual-options-panel', swatchVisualOption); + }; +}); diff --git a/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js b/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js index c6feb68cd6265f68a2f2d9cca6f6648f7c50dc4f..df8fa96a64910f32363ef0abac90c712b6127a95 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/SwatchRenderer.js @@ -173,15 +173,15 @@ define(["jquery", "jquery/ui"], function ($) { moreButton: 'swatch-more', loader: 'swatch-option-loading' }, - jsonConfig: {}, // option's json config - jsonSwatchConfig: {}, // swatch's json config - selectorProduct: '.product-info-main', // selector of parental block of prices and swatches (need to know where to seek for price block) - selectorProductPrice: '.price', // selector of price wrapper (need to know where set price) - numberToShow: false, // number of controls to show (false or zero = show all) - onlySwatches: false, // show only swatch controls - enableControlLabel: true, // enable label for control - moreButtonText: 'More', // text for more button - mediaCallback: '' // Callback url for media + jsonConfig: {}, // option's json config + jsonSwatchConfig: {}, // swatch's json config + selectorProduct: '.product-info-main', // selector of parental block of prices and swatches (need to know where to seek for price block) + selectorProductPrice: '.price-final-price .price', // selector of price wrapper (need to know where set price) + numberToShow: false, // number of controls to show (false or zero = show all) + onlySwatches: false, // show only swatch controls + enableControlLabel: true, // enable label for control + moreButtonText: 'More', // text for more button + mediaCallback: '' // Callback url for media }, /** diff --git a/app/code/Magento/Tax/Pricing/Render/Adjustment.php b/app/code/Magento/Tax/Pricing/Render/Adjustment.php index db800ac1c16346d8e5fd7148cb89f6ad91908a86..13bb610542402927805bcbb6e6c2dba65a370aa0 100644 --- a/app/code/Magento/Tax/Pricing/Render/Adjustment.php +++ b/app/code/Magento/Tax/Pricing/Render/Adjustment.php @@ -23,8 +23,8 @@ class Adjustment extends AbstractAdjustment /** * @param Template\Context $context - * @param \Magento\Tax\Helper\Data $helper * @param PriceCurrencyInterface $priceCurrency + * @param \Magento\Tax\Helper\Data $helper * @param array $data */ public function __construct( @@ -69,7 +69,6 @@ class Adjustment extends AbstractAdjustment */ public function getAdjustmentCode() { - //@TODO We can build two model using DI, not code. What about passing it in constructor? return \Magento\Tax\Pricing\Adjustment::ADJUSTMENT_CODE; } @@ -86,14 +85,14 @@ class Adjustment extends AbstractAdjustment /** * Obtain display amount excluding tax * + * @param array $exclude * @param bool $includeContainer * @return string */ - public function getDisplayAmountExclTax($includeContainer = false) + public function getDisplayAmountExclTax($exclude = null, $includeContainer = false) { - // todo use 'excludeWith' method instead hard-coded list here return $this->formatCurrency( - $this->getRawAmount(['tax', 'weee']), + $this->getRawAmount($exclude), $includeContainer ); } @@ -104,11 +103,26 @@ class Adjustment extends AbstractAdjustment * @param array $exclude * @return float */ - public function getRawAmount($exclude = ['tax', 'weee']) + public function getRawAmount($exclude = null) { + //If exclude is not supplied, use the default + if ($exclude === null) { + $exclude = $this->getDefaultExclusions(); + } + return $this->amountRender->getAmount()->getValue($exclude); } + /** + * Returns the list of default exclusions + * + * @return array + */ + public function getDefaultExclusions() + { + return [$this->getAdjustmentCode()]; + } + /** * Obtain display amount * diff --git a/app/code/Magento/Tax/Setup/InstallData.php b/app/code/Magento/Tax/Setup/InstallData.php index d62ae4e22dc7a51a0c7f0ef1543ddefb52ddc8d3..2395d0aa12139f1232c7e61a71c2a7345952c3c7 100644 --- a/app/code/Magento/Tax/Setup/InstallData.php +++ b/app/code/Magento/Tax/Setup/InstallData.php @@ -38,13 +38,12 @@ class InstallData implements InstallDataInterface public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { /** @var TaxSetup $taxSetup */ - $taxSetup = $this->taxSetupFactory->create(['setup' => $setup]); + $taxSetup = $this->taxSetupFactory->create(['resourceName' => 'tax_setup', 'setup' => $setup]); /** * Add tax_class_id attribute to the 'eav_attribute' table */ - $catalogInstaller = $taxSetup->getCatalogSetup(['resourceName' => 'catalog_setup', 'setup' => $setup]); - $catalogInstaller->addAttribute( + $taxSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'tax_class_id', [ @@ -66,7 +65,7 @@ class InstallData implements InstallDataInterface 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, - 'visible_in_advanced_search' => true, + 'visible_in_advanced_search' => false, 'used_in_product_listing' => true, 'unique' => false, 'apply_to' => implode(',', $taxSetup->getTaxableItems()), diff --git a/app/code/Magento/Tax/Setup/InstallSchema.php b/app/code/Magento/Tax/Setup/InstallSchema.php index 9d53d465bc17918c6423b7c0f1e64ca57ba55b7c..5cfd6763f2d6b82bdfa876f9061a1d7d0cd4b2a9 100644 --- a/app/code/Magento/Tax/Setup/InstallSchema.php +++ b/app/code/Magento/Tax/Setup/InstallSchema.php @@ -405,6 +405,5 @@ class InstallSchema implements InstallSchemaInterface ); $setup->endSetup(); - } } diff --git a/app/code/Magento/Tax/Setup/TaxSetup.php b/app/code/Magento/Tax/Setup/TaxSetup.php index f55ade276e145c4f5ab2e52f6d1da507f240d1e2..fd5cb63739289d47a6576685372805ad7ab70f34 100644 --- a/app/code/Magento/Tax/Setup/TaxSetup.php +++ b/app/code/Magento/Tax/Setup/TaxSetup.php @@ -6,26 +6,18 @@ namespace Magento\Tax\Setup; use Magento\Catalog\Model\ProductTypes\ConfigInterface; -use Magento\Catalog\Setup\CategorySetup; -use Magento\Catalog\Setup\CategorySetupFactory; -use Magento\Eav\Model\Entity\Setup\Context; -use Magento\Eav\Model\Resource\Entity\Attribute\Group\CollectionFactory; -use Magento\Framework\App\CacheInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Sales\Setup\SalesSetup; +use Magento\Sales\Setup\SalesSetupFactory; /** * Tax Setup Resource Model */ -class TaxSetup extends SalesSetup +class TaxSetup { /** - * Category setup factory - * - * @var CategorySetupFactory + * @var \Magento\Sales\Setup\SalesSetup */ - protected $_setupFactory; + protected $salesSetup; /** * Product type config @@ -38,44 +30,55 @@ class TaxSetup extends SalesSetup * Init * * @param ModuleDataSetupInterface $setup - * @param Context $context - * @param CacheInterface $cache - * @param CollectionFactory $attrGroupCollectionFactory - * @param ScopeConfigInterface $config - * @param CategorySetupFactory $setupFactory + * @param SalesSetupFactory $salesSetupFactory * @param ConfigInterface $productTypeConfig */ public function __construct( ModuleDataSetupInterface $setup, - Context $context, - CacheInterface $cache, - CollectionFactory $attrGroupCollectionFactory, - ScopeConfigInterface $config, - CategorySetupFactory $setupFactory, + SalesSetupFactory $salesSetupFactory, ConfigInterface $productTypeConfig ) { - $this->_setupFactory = $setupFactory; + $this->salesSetup = $salesSetupFactory->create(['resourceName' => 'tax_setup', 'setup' => $setup]); $this->productTypeConfig = $productTypeConfig; } /** - * Gets catalog setup + * Get taxable product types * - * @param array $data - * @return CategorySetup + * @return array */ - public function getCatalogSetup(array $data = []) + public function getTaxableItems() { - return $this->_setupFactory->create($data); + return $this->productTypeConfig->filter('taxable'); } /** - * Get taxable product types + * Add entity attribute. * - * @return array + * @param int|string $entityTypeId + * @param string $code + * @param array $attr + * @return $this */ - public function getTaxableItems() + public function addAttribute($entityTypeId, $code, array $attr) { - return $this->productTypeConfig->filter('taxable'); + //Delegate + return $this->salesSetup->addAttribute($entityTypeId, $code, $attr); + } + + /** + * Update Attribute data and Attribute additional data. + * + * @param int|string $entityTypeId + * @param int|string $id + * @param string $field + * @param mixed $value + * @param int $sortOrder + * @return $this + */ + public function updateAttribute($entityTypeId, $id, $field, $value = null, $sortOrder = null) + { + //Delegate + return $this->salesSetup->updateAttribute($entityTypeId, $id, $field, $value, $sortOrder); } } diff --git a/app/code/Magento/Tax/Setup/UpgradeData.php b/app/code/Magento/Tax/Setup/UpgradeData.php new file mode 100644 index 0000000000000000000000000000000000000000..39b39d21db394e68f32367b0a6865f61a61102b7 --- /dev/null +++ b/app/code/Magento/Tax/Setup/UpgradeData.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Tax\Setup; + +use Magento\Framework\Setup\UpgradeDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; + +/** + * @codeCoverageIgnore + */ +class UpgradeData implements UpgradeDataInterface +{ + /** + * Tax setup factory + * + * @var TaxSetupFactory + */ + private $taxSetupFactory; + + /** + * Init + * + * @param TaxSetupFactory $taxSetupFactory + */ + public function __construct(TaxSetupFactory $taxSetupFactory) + { + $this->taxSetupFactory = $taxSetupFactory; + } + + /** + * {@inheritdoc} + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + /** @var TaxSetup $taxSetup */ + $taxSetup = $this->taxSetupFactory->create(['resourceName' => 'tax_setup', 'setup' => $setup]); + + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + //Update the tax_class_id attribute in the 'catalog_eav_attribute' table + $taxSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'tax_class_id', + 'is_visible_in_advanced_search', + false + ); + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Tax/Test/Unit/Pricing/Render/AdjustmentTest.php b/app/code/Magento/Tax/Test/Unit/Pricing/Render/AdjustmentTest.php index 36327474d63170eb331728ea1c8c790dc27a075c..197dc6759cc9d9620ab45319c63c23545d9f950a 100644 --- a/app/code/Magento/Tax/Test/Unit/Pricing/Render/AdjustmentTest.php +++ b/app/code/Magento/Tax/Test/Unit/Pricing/Render/AdjustmentTest.php @@ -91,6 +91,16 @@ class AdjustmentTest extends \PHPUnit_Framework_TestCase $this->assertEquals(\Magento\Tax\Pricing\Adjustment::ADJUSTMENT_CODE, $this->model->getAdjustmentCode()); } + /** + * Test for method getDefaultExclusions + */ + public function testGetDefaultExclusions() + { + $defaultExclusions = $this->model->getDefaultExclusions(); + $this->assertNotEmpty($defaultExclusions, 'Expected to have at least one default exclusion'); + $this->assertContains($this->model->getAdjustmentCode(), $defaultExclusions); + } + /** * Test for method displayBothPrices */ diff --git a/app/code/Magento/Tax/etc/adminhtml/system.xml b/app/code/Magento/Tax/etc/adminhtml/system.xml index 23fc72d7d47e491e56cacb957db9b237a9487347..33a83ffb1c5a02f67bb91a59b5ada6e27c98be32 100644 --- a/app/code/Magento/Tax/etc/adminhtml/system.xml +++ b/app/code/Magento/Tax/etc/adminhtml/system.xml @@ -118,7 +118,7 @@ <backend_model>Magento\Tax\Model\Config\Notification</backend_model> </field> <field id="grandtotal" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Include Tax In Grand Total</label> + <label>Include Tax In Order Total</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="full_summary" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1"> @@ -148,7 +148,7 @@ <backend_model>Magento\Tax\Model\Config\Notification</backend_model> </field> <field id="grandtotal" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Include Tax In Grand Total</label> + <label>Include Tax In Order Total</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="full_summary" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1"> diff --git a/app/code/Magento/Tax/etc/module.xml b/app/code/Magento/Tax/etc/module.xml index 865aedbf33777db01851db8386cecdef83bbd56a..4e7d0511fc04f8ffca219912076791b2ecc31d1f 100644 --- a/app/code/Magento/Tax/etc/module.xml +++ b/app/code/Magento/Tax/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> - <module name="Magento_Tax" setup_version="2.0.0"> + <module name="Magento_Tax" setup_version="2.0.1"> <sequence> <module name="Magento_Catalog"/> <module name="Magento_Customer"/> diff --git a/app/code/Magento/Tax/view/base/templates/pricing/adjustment.phtml b/app/code/Magento/Tax/view/base/templates/pricing/adjustment.phtml index 7b36a19ffb66f9b23774f2e68a928afd05b8dc69..f46a39f534c0e5a2afd308f74cf796b3b78c8d77 100644 --- a/app/code/Magento/Tax/view/base/templates/pricing/adjustment.phtml +++ b/app/code/Magento/Tax/view/base/templates/pricing/adjustment.phtml @@ -10,10 +10,6 @@ <?php /** @var \Magento\Tax\Pricing\Render\Adjustment $block */ ?> -<?php -//getAdjustmentCode -?> - <?php if ($block->displayBothPrices()): ?> <span id="<?php /* @escapeNotVerified */ echo $block->buildIdWithPrefix('price-excluding-tax-') ?>" data-label="<?php echo $block->escapeHtml(__('Excl. Tax')); ?>" diff --git a/app/code/Magento/Tax/view/frontend/layout/checkout_cart_index.xml b/app/code/Magento/Tax/view/frontend/layout/checkout_cart_index.xml index 100fbe55787c5d4bdebc3282c9176fc9a5abc83b..b7bb9e2b2ec8badfac515d1468762612cb334262 100644 --- a/app/code/Magento/Tax/view/frontend/layout/checkout_cart_index.xml +++ b/app/code/Magento/Tax/view/frontend/layout/checkout_cart_index.xml @@ -48,9 +48,9 @@ <item name="component" xsi:type="string">Magento_Tax/js/view/checkout/cart/totals/grand-total</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Tax/checkout/cart/totals/grand-total</item> - <item name="exclTaxLabel" xsi:type="string">Grand Total Excl. Tax</item> - <item name="inclTaxLabel" xsi:type="string">Grand Total Incl. Tax</item> - <item name="title" xsi:type="string">Grand Total</item> + <item name="exclTaxLabel" xsi:type="string">Order Total Excl. Tax</item> + <item name="inclTaxLabel" xsi:type="string">Order Total Incl. Tax</item> + <item name="title" xsi:type="string">Order Total</item> </item> </item> </item> diff --git a/app/code/Magento/Theme/Model/View/Design.php b/app/code/Magento/Theme/Model/View/Design.php index d012f0117313e49791d12b6ed56d512cea509af3..321ed683d3c3691b00f41b11ede7d14dbc863376 100644 --- a/app/code/Magento/Theme/Model/View/Design.php +++ b/app/code/Magento/Theme/Model/View/Design.php @@ -169,7 +169,7 @@ class Design implements \Magento\Framework\View\DesignInterface $theme = null; $store = isset($params['store']) ? $params['store'] : null; - if ($this->_isThemePerStoveView($area)) { + if ($this->_isThemePerStoreView($area)) { if ($this->_storeManager->isSingleStoreMode()) { $theme = $this->_scopeConfig->getValue( self::XML_PATH_THEME_ID, @@ -197,7 +197,7 @@ class Design implements \Magento\Framework\View\DesignInterface * @param string $area * @return bool */ - private function _isThemePerStoveView($area) + private function _isThemePerStoreView($area) { return $area == self::DEFAULT_AREA; } diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index e8da51653dbcbd691b2fe595c9fd3c8a525089db..6b32e65b7ec19a9caa7b1af6ae4029b3b4ca9f11 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -82,7 +82,7 @@ <type name="Magento\Theme\Model\View\Design"> <arguments> <argument name="themes" xsi:type="array"> - <item name="frontend" xsi:type="string">Magento/blank</item> + <item name="frontend" xsi:type="string">Magento/luma</item> <item name="adminhtml" xsi:type="string">Magento/backend</item> </argument> </arguments> diff --git a/app/code/Magento/Theme/view/adminhtml/page_layout/admin-login.xml b/app/code/Magento/Theme/view/adminhtml/page_layout/admin-login.xml index 5c7c757a560dfd7ec48b78f5701e373d0a85674f..bba3e8c3cc482d1c4bd6975edc579ed3714a1571 100644 --- a/app/code/Magento/Theme/view/adminhtml/page_layout/admin-login.xml +++ b/app/code/Magento/Theme/view/adminhtml/page_layout/admin-login.xml @@ -7,6 +7,7 @@ --> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_layout.xsd"> <container name="root" htmlTag="section" htmlClass="page-wrapper"> + <container name="after.body.start" as="after.body.start" label="Page Top" before="-"/> <container name="login.header" htmlTag="header" htmlClass="login-header"/> <container name="login.content" htmlTag="div" htmlClass="login-content"/> <container name="login.footer" htmlTag="footer" htmlClass="login-footer"/> diff --git a/app/code/Magento/Ui/Component/Filters/FilterModifier.php b/app/code/Magento/Ui/Component/Filters/FilterModifier.php new file mode 100644 index 0000000000000000000000000000000000000000..d1db57a2ac49488c5f43cb516fab85de1c910185 --- /dev/null +++ b/app/code/Magento/Ui/Component/Filters/FilterModifier.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Ui\Component\Filters; + +use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Api\FilterBuilder; + +/** + * Apply modifiers to filter + */ +class FilterModifier +{ + /** + * Filter modifier variable name + */ + const FILTER_MODIFIER = 'filters_modifier'; + + /** @var RequestInterface */ + protected $request; + + /** @var FilterBuilder */ + protected $filterBuilder; + + /** @var array */ + protected $allowedConditionTypes; + + /** + * @param RequestInterface $request + * @param FilterBuilder $filterBuilder + * @param array $allowedConditionTypes + */ + public function __construct(RequestInterface $request, FilterBuilder $filterBuilder, $allowedConditionTypes = []) + { + $this->request = $request; + $this->filterBuilder = $filterBuilder; + $this->allowedConditionTypes = array_merge( + ['eq', 'neq', 'in', 'nin', 'null', 'notnull'], + $allowedConditionTypes + ); + } + + /** + * Apply modifiers for filters + * + * @param DataProviderInterface $dataProvider + * @param string $filterName + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function applyFilterModifier(DataProviderInterface $dataProvider, $filterName) + { + $filterModifier = $this->request->getParam(self::FILTER_MODIFIER); + if (isset($filterModifier[$filterName]['condition_type'])) { + $conditionType = $filterModifier[$filterName]['condition_type']; + if (!in_array($conditionType, $this->allowedConditionTypes)) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Condition type "%1" is not allowed', $conditionType) + ); + } + $value = isset($filterModifier[$filterName]['value']) + ? $filterModifier[$filterName]['value'] + : null; + $filter = $this->filterBuilder->setConditionType($conditionType) + ->setField($filterName) + ->setValue($value) + ->create(); + $dataProvider->addFilter($filter); + } + } +} diff --git a/app/code/Magento/Ui/Component/Filters/Type/AbstractFilter.php b/app/code/Magento/Ui/Component/Filters/Type/AbstractFilter.php index 02b8767c3e263d3d03d27e34441be4d58fdad407..d5642acc8666b9651ea419d57b72d5b40a72e29c 100644 --- a/app/code/Magento/Ui/Component/Filters/Type/AbstractFilter.php +++ b/app/code/Magento/Ui/Component/Filters/Type/AbstractFilter.php @@ -9,6 +9,7 @@ use Magento\Ui\Component\AbstractComponent; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\Api\FilterBuilder; +use Magento\Ui\Component\Filters\FilterModifier; /** * Abstract class AbstractFilter @@ -42,10 +43,16 @@ abstract class AbstractFilter extends AbstractComponent */ protected $filterBuilder; + /** + * @var FilterModifier + */ + protected $filterModifier; + /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param FilterBuilder $filterBuilder + * @param FilterModifier $filterModifier * @param array $components * @param array $data */ @@ -53,6 +60,7 @@ abstract class AbstractFilter extends AbstractComponent ContextInterface $context, UiComponentFactory $uiComponentFactory, FilterBuilder $filterBuilder, + FilterModifier $filterModifier, array $components = [], array $data = [] ) { @@ -60,6 +68,7 @@ abstract class AbstractFilter extends AbstractComponent $this->filterBuilder = $filterBuilder; parent::__construct($context, $components, $data); $this->filterData = $this->getContext()->getFiltersParams(); + $this->filterModifier = $filterModifier; } /** @@ -71,4 +80,13 @@ abstract class AbstractFilter extends AbstractComponent { return static::NAME; } + + /** + * {@inheritdoc} + */ + public function prepare() + { + $this->filterModifier->applyFilterModifier($this->getContext()->getDataProvider(), $this->getName()); + parent::prepare(); + } } diff --git a/app/code/Magento/Ui/Component/Filters/Type/Select.php b/app/code/Magento/Ui/Component/Filters/Type/Select.php index 5b26dfc57daa514600aee606e2db588dfd46f816..8b826799993e988b673fce88702035f48042c9e8 100644 --- a/app/code/Magento/Ui/Component/Filters/Type/Select.php +++ b/app/code/Magento/Ui/Component/Filters/Type/Select.php @@ -9,6 +9,7 @@ use Magento\Framework\Data\OptionSourceInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Ui\Component\Form\Element\Select as ElementSelect; +use Magento\Ui\Component\Filters\FilterModifier; /** * Class Select @@ -35,6 +36,7 @@ class Select extends AbstractFilter * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param \Magento\Framework\Api\FilterBuilder $filterBuilder + * @param FilterModifier $filterModifier * @param OptionSourceInterface|null $optionsProvider * @param array $components * @param array $data @@ -43,12 +45,13 @@ class Select extends AbstractFilter ContextInterface $context, UiComponentFactory $uiComponentFactory, \Magento\Framework\Api\FilterBuilder $filterBuilder, + FilterModifier $filterModifier, OptionSourceInterface $optionsProvider = null, array $components = [], array $data = [] ) { $this->optionsProvider = $optionsProvider; - parent::__construct($context, $uiComponentFactory, $filterBuilder, $components, $data); + parent::__construct($context, $uiComponentFactory, $filterBuilder, $filterModifier, $components, $data); } /** diff --git a/app/code/Magento/Ui/Component/Layout.php b/app/code/Magento/Ui/Component/Layout.php index 2dd070f4f2d5445e9d8b46583a35a2b0b6ed511f..ab2a4b53a735001b5adaa8b8ee054390b48562e6 100644 --- a/app/code/Magento/Ui/Component/Layout.php +++ b/app/code/Magento/Ui/Component/Layout.php @@ -5,7 +5,7 @@ */ namespace Magento\Ui\Component; -use Magento\Ui\Component\Layout\LayoutPool; +use Magento\Framework\View\Layout\Pool as LayoutPool; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\UiComponent\LayoutInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; diff --git a/app/code/Magento/Ui/Component/Layout/Tabs.php b/app/code/Magento/Ui/Component/Layout/Tabs.php index cd36ed90824792cdf68aaf97201d356f31a1741a..34021f0d8782d49a31647f87d92f114591304491 100644 --- a/app/code/Magento/Ui/Component/Layout/Tabs.php +++ b/app/code/Magento/Ui/Component/Layout/Tabs.php @@ -5,7 +5,6 @@ */ namespace Magento\Ui\Component\Layout; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\UiComponent\DataSourceInterface; use Magento\Ui\Component\Layout\Tabs\TabInterface; @@ -16,7 +15,7 @@ use Magento\Framework\View\Element\UiComponent\LayoutInterface; /** * Class Tabs */ -class Tabs extends Generic implements LayoutInterface +class Tabs extends \Magento\Framework\View\Layout\Generic implements LayoutInterface { /** * @var string diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index 8060896296318c65ead5875a90b36e04ac6cdc0c..e2138de2221a918ceeeb17f25665d6d140cc1d13 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -5,7 +5,7 @@ */ namespace Magento\Ui\TemplateEngine\Xhtml; -use Magento\Ui\Component\Layout\Generator\Structure; +use Magento\Framework\View\Layout\Generator\Structure; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\View\TemplateEngine\Xhtml\Template; use Magento\Framework\View\TemplateEngine\Xhtml\ResultInterface; diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/FilterModifierTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/FilterModifierTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a7f954bac61396b1fe69198cab98816234f29d0f --- /dev/null +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/FilterModifierTest.php @@ -0,0 +1,139 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Ui\Test\Unit\Component\Filters; + +use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; + +/** + * Class DateRangeTest + */ +class FilterModifierTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * @var DataProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dataProvider; + + /** + * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterBuilder; + + /** + * @var \Magento\Ui\Component\Filters\FilterModifier + */ + protected $unit; + + /** + * Set up + */ + public function setUp() + { + $this->request = $this->getMockForAbstractClass('Magento\Framework\App\RequestInterface'); + $this->dataProvider = $this->getMockForAbstractClass( + 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface' + ); + $this->filterBuilder = $this->getMock( + 'Magento\Framework\Api\FilterBuilder', + [], + [], + '', + false + ); + $this->unit = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this)) + ->getObject( + 'Magento\Ui\Component\Filters\FilterModifier', + [ + 'request' => $this->request, + 'filterBuilder' => $this->filterBuilder, + ] + ); + } + + /** + * @return void + */ + public function testNotApplyFilterModifier() + { + $this->request->expects($this->once())->method('getParam') + ->with(\Magento\Ui\Component\Filters\FilterModifier::FILTER_MODIFIER) + ->willReturn([]); + $this->dataProvider->expects($this->never())->method('addFilter'); + $this->unit->applyFilterModifier($this->dataProvider, 'test'); + } + + /** + * @return void + * @assertException \Magento\Framework\Exception\LocalizedException + */ + public function testApplyFilterModifierWithNotAllowedCondition() + { + $this->request->expects($this->once())->method('getParam') + ->with(\Magento\Ui\Component\Filters\FilterModifier::FILTER_MODIFIER) + ->willReturn([ + 'filter' => [ + 'condition_type' => 'not_allowed' + ] + ]); + $this->dataProvider->expects($this->never())->method('addFilter'); + $this->unit->applyFilterModifier($this->dataProvider, 'test'); + } + + /** + * @param $filterModifier + * @param $filterName + * @param $conditionType + * @param $value + * @return void + * @dataProvider getApplyFilterModifierDataProvider + */ + public function testApplyFilterModifierWith($filterModifier, $filterName, $conditionType, $value) + { + $filter = $this->getMock('Magento\Framework\Api\Filter'); + + $this->request->expects($this->once())->method('getParam') + ->with(\Magento\Ui\Component\Filters\FilterModifier::FILTER_MODIFIER) + ->willReturn($filterModifier); + $this->filterBuilder->expects($this->once())->method('setConditionType')->with($conditionType) + ->willReturnSelf(); + $this->filterBuilder->expects($this->once())->method('setField')->with($filterName)->willReturnSelf(); + $this->filterBuilder->expects($this->once())->method('setValue')->with($value)->willReturnSelf(); + $this->filterBuilder->expects($this->once())->method('create')->with()->willReturn($filter); + $this->dataProvider->expects($this->once())->method('addFilter')->with($filter); + + $this->unit->applyFilterModifier($this->dataProvider, $filterName); + } + + /** + * @return array + */ + public function getApplyFilterModifierDataProvider() + { + return [ + [ + [ + 'filter1' => ['condition_type' => 'eq', 'value' => '5',] + ], + 'filter1', + 'eq', + '5' + ], + [ + [ + 'filter2' => ['condition_type' => 'notnull'] + ], + 'filter2', + 'notnull', + null + ], + ]; + } +} diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateRangeTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateRangeTest.php index 36bc19158e9701bbeacaf0d642d569a29658d630..6a2a0b545678dc66181b408795e15be8bebbba4d 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateRangeTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateRangeTest.php @@ -32,6 +32,11 @@ class DateRangeTest extends \PHPUnit_Framework_TestCase */ protected $filterBuilderMock; + /** + * @var \Magento\Ui\Component\Filters\FilterModifier|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterModifierMock; + /** * Set up */ @@ -57,6 +62,13 @@ class DateRangeTest extends \PHPUnit_Framework_TestCase '', false ); + $this->filterModifierMock = $this->getMock( + 'Magento\Ui\Component\Filters\FilterModifier', + ['applyFilterModifier'], + [], + '', + false + ); } /** @@ -70,6 +82,7 @@ class DateRangeTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [] ); $this->assertTrue($dateRange->getComponentName() === DateRange::NAME); @@ -109,22 +122,22 @@ class DateRangeTest extends \PHPUnit_Framework_TestCase ->method('getRequestParam') ->with(UiContext::FILTER_VAR) ->willReturn($filterData); + $dataProvider = $this->getMockForAbstractClass( + 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', + [], + '', + false + ); + $this->contextMock->expects($this->any()) + ->method('getDataProvider') + ->willReturn($dataProvider); if ($expectedCondition !== null) { /** @var DataProviderInterface $dataProvider */ - $dataProvider = $this->getMockForAbstractClass( - 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', - [], - '', - false - ); $dataProvider->expects($this->any()) ->method('addFilter') ->with($expectedCondition, $name); - $this->contextMock->expects($this->any()) - ->method('getDataProvider') - ->willReturn($dataProvider); $uiComponent->expects($this->any()) ->method('getLocale') @@ -143,6 +156,7 @@ class DateRangeTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [], ['name' => $name] ); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateTest.php index 3e76608a50743cf1e6a6f4e54f601e7b819c7d76..79af8056579f644d965f348b20302f9c8dd122b6 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/DateTest.php @@ -32,6 +32,11 @@ class DateTest extends \PHPUnit_Framework_TestCase */ protected $filterBuilderMock; + /** + * @var \Magento\Ui\Component\Filters\FilterModifier|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterModifierMock; + /** * Set up */ @@ -57,6 +62,15 @@ class DateTest extends \PHPUnit_Framework_TestCase '', false ); + + $this->filterModifierMock = $this->getMock( + 'Magento\Ui\Component\Filters\FilterModifier', + ['applyFilterModifier'], + [], + '', + false + ); + } /** @@ -70,6 +84,7 @@ class DateTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [] ); @@ -110,23 +125,20 @@ class DateTest extends \PHPUnit_Framework_TestCase ->method('getRequestParam') ->with(UiContext::FILTER_VAR) ->willReturn($filterData); - + $dataProvider = $this->getMockForAbstractClass( + 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', + [], + '', + false + ); + $this->contextMock->expects($this->any()) + ->method('getDataProvider') + ->willReturn($dataProvider); if ($expectedCondition !== null) { - /** @var DataProviderInterface $dataProvider */ - $dataProvider = $this->getMockForAbstractClass( - 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', - [], - '', - false - ); $dataProvider->expects($this->any()) ->method('addFilter') ->with($expectedCondition, $name); - $this->contextMock->expects($this->any()) - ->method('getDataProvider') - ->willReturn($dataProvider); - $uiComponent->expects($this->any()) ->method('getLocale') ->willReturn($expectedCondition['locale']); @@ -144,6 +156,7 @@ class DateTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [], ['name' => $name] ); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php index cf33a86653fd73caa96f8c58934f65582c7abd53..a3736ad6bd5028bbfc4e2201253a6aaa4043a59c 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php @@ -32,6 +32,11 @@ class InputTest extends \PHPUnit_Framework_TestCase */ protected $filterBuilderMock; + /** + * @var \Magento\Ui\Component\Filters\FilterModifier|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterModifierMock; + /** * Set up */ @@ -57,6 +62,13 @@ class InputTest extends \PHPUnit_Framework_TestCase '', false ); + $this->filterModifierMock = $this->getMock( + 'Magento\Ui\Component\Filters\FilterModifier', + ['applyFilterModifier'], + [], + '', + false + ); } /** @@ -70,6 +82,7 @@ class InputTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [] ); @@ -109,22 +122,19 @@ class InputTest extends \PHPUnit_Framework_TestCase ->method('getRequestParam') ->with(UiContext::FILTER_VAR) ->willReturn($filterData); - + $dataProvider = $this->getMockForAbstractClass( + 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', + [], + '', + false + ); + $this->contextMock->expects($this->any()) + ->method('getDataProvider') + ->willReturn($dataProvider); if ($expectedCondition !== null) { - /** @var DataProviderInterface $dataProvider */ - $dataProvider = $this->getMockForAbstractClass( - 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', - [], - '', - false - ); $dataProvider->expects($this->any()) ->method('addFilter') ->with($expectedCondition, $name); - - $this->contextMock->expects($this->any()) - ->method('getDataProvider') - ->willReturn($dataProvider); } $this->uiComponentFactory->expects($this->any()) @@ -136,6 +146,7 @@ class InputTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [], ['name' => $name] ); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/RangeTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/RangeTest.php index b3da88e49eb4bddc5519d184bd67d4ced2446aa0..d28cb78ee21c9e8dd2535003786193975c1e9f45 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/RangeTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/RangeTest.php @@ -31,6 +31,11 @@ class RangeTest extends \PHPUnit_Framework_TestCase */ protected $filterBuilderMock; + /** + * @var \Magento\Ui\Component\Filters\FilterModifier|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterModifierMock; + /** * Set up */ @@ -57,6 +62,13 @@ class RangeTest extends \PHPUnit_Framework_TestCase '', false ); + $this->filterModifierMock = $this->getMock( + 'Magento\Ui\Component\Filters\FilterModifier', + ['applyFilterModifier'], + [], + '', + false + ); } /** @@ -70,6 +82,7 @@ class RangeTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [] ); @@ -97,28 +110,27 @@ class RangeTest extends \PHPUnit_Framework_TestCase ->method('getRequestParam') ->with(UiContext::FILTER_VAR) ->willReturn($filterData); - + /** @var DataProviderInterface $dataProvider */ + $dataProvider = $this->getMockForAbstractClass( + 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', + [], + '', + false + ); + $this->contextMock->expects($this->any()) + ->method('getDataProvider') + ->willReturn($dataProvider); if ($expectedCondition !== null) { - /** @var DataProviderInterface $dataProvider */ - $dataProvider = $this->getMockForAbstractClass( - 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', - [], - '', - false - ); $dataProvider->expects($this->any()) ->method('addFilter') ->with($expectedCondition, $name); - - $this->contextMock->expects($this->any()) - ->method('getDataProvider') - ->willReturn($dataProvider); } $range = new Range( $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, [], ['name' => $name] ); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php index 24237520a321ba1552216d01f8c4cb0dd43f56a2..45b200fc58494f0964058447e5fe04cb97a5de94 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/SelectTest.php @@ -32,6 +32,11 @@ class SelectTest extends \PHPUnit_Framework_TestCase */ protected $filterBuilderMock; + /** + * @var \Magento\Ui\Component\Filters\FilterModifier|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterModifierMock; + /** * Set up */ @@ -57,6 +62,13 @@ class SelectTest extends \PHPUnit_Framework_TestCase '', false ); + $this->filterModifierMock = $this->getMock( + 'Magento\Ui\Component\Filters\FilterModifier', + ['applyFilterModifier'], + [], + '', + false + ); } /** @@ -70,6 +82,7 @@ class SelectTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, null, [] ); @@ -110,22 +123,21 @@ class SelectTest extends \PHPUnit_Framework_TestCase ->method('getRequestParam') ->with(AbstractFilter::FILTER_VAR) ->willReturn($filterData); + /** @var DataProviderInterface $dataProvider */ + $dataProvider = $this->getMockForAbstractClass( + 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', + [], + '', + false + ); + $this->contextMock->expects($this->any()) + ->method('getDataProvider') + ->willReturn($dataProvider); if ($expectedCondition !== null) { - /** @var DataProviderInterface $dataProvider */ - $dataProvider = $this->getMockForAbstractClass( - 'Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface', - [], - '', - false - ); $dataProvider->expects($this->any()) ->method('addFilter') ->with($expectedCondition, $name); - - $this->contextMock->expects($this->any()) - ->method('getDataProvider') - ->willReturn($dataProvider); } /** @var \Magento\Framework\Data\OptionSourceInterface $selectOptions */ @@ -145,6 +157,7 @@ class SelectTest extends \PHPUnit_Framework_TestCase $this->contextMock, $this->uiComponentFactory, $this->filterBuilderMock, + $this->filterModifierMock, $selectOptions, [], ['name' => $name] diff --git a/app/code/Magento/Ui/etc/di.xml b/app/code/Magento/Ui/etc/di.xml index a29e842f3eaab3f48a9eca422b6f474d416e7ea4..a96eecf8ad05e1481ee9a7e1313809f7a2ffa3a4 100644 --- a/app/code/Magento/Ui/etc/di.xml +++ b/app/code/Magento/Ui/etc/di.xml @@ -12,7 +12,7 @@ <preference for="Magento\Framework\Config\ConverterInterface" type="Magento\Framework\View\Element\UiComponent\Config\Converter" /> <preference for="Magento\Framework\View\Element\UiComponent\Config\ManagerInterface" type="Magento\Ui\Model\Manager" /> <preference for="Magento\Framework\View\Element\UiComponent\ContextInterface" type="Magento\Framework\View\Element\UiComponent\Context" /> - <preference for="Magento\Framework\View\Element\UiComponent\LayoutInterface" type="Magento\Ui\Component\Layout\Generic"/> + <preference for="Magento\Framework\View\Element\UiComponent\LayoutInterface" type="Magento\Framework\View\Layout\Generic"/> <preference for="Magento\Authorization\Model\UserContextInterface" type="Magento\User\Model\Authorization\AdminSessionUserContext"/> <preference for="Magento\Ui\Api\Data\BookmarkSearchResultsInterface" type="Magento\Framework\Api\SearchResults" /> <preference for="Magento\Ui\Api\BookmarkRepositoryInterface" type="Magento\Ui\Model\Resource\BookmarkRepository"/> @@ -153,11 +153,11 @@ </argument> </arguments> </type> - <type name="Magento\Ui\Component\Layout\LayoutPool"> + <type name="Magento\Framework\View\Layout\Pool"> <arguments> <argument name="types" xsi:type="array"> <item name="generic" xsi:type="array"> - <item name="class" xsi:type="string">Magento\Ui\Component\Layout\Generic</item> + <item name="class" xsi:type="string">Magento\Framework\View\Layout\Generic</item> <item name="template" xsi:type="string">templates/layout/generic</item> </item> <item name="tabs" xsi:type="array"> diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js index f37832bc8972eddc3bf8e31b4a8d095e7bcdcff0..091cd9ee2ea570a238e72a1a27cb9eb36d579322 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js @@ -2,6 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ + define([ 'underscore', 'mageUtils', @@ -35,7 +36,7 @@ define([ */ initObservable: function () { this._super() - .observe('actions opened'); + .observe('actions'); return this; }, @@ -253,40 +254,6 @@ define([ var action = this.getAction(rowIndex, actionIndex); return _.isObject(action.callback) || action.confirm || !action.href; - }, - - /** - * Opens or closes specific actions list. - * - * @param {Number} rowIndex - Index of a row, - * where actions are displayed. - * @returns {ActionsColumn} Chainable. - */ - toggleList: function (rowIndex) { - var state = false; - - if (rowIndex !== this.opened()) { - state = rowIndex; - } - - this.opened(state); - - return this; - }, - - /** - * Closes actions list. - * - * @param {Number} rowIndex - Index of a row, - * where actions are displayed. - * @returns {ActionsColumn} - */ - closeList: function (rowIndex) { - if (this.opened() === rowIndex) { - this.opened(false); - } - - return this; } }); }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js index 80e50c62db9e598eb94ab98cf2bc840683e64aa5..b48f1c9ed8a35adc457b2f03b0190a2cb4d7c3d2 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js @@ -19,6 +19,7 @@ define([ allSelected: false, indetermine: false, selected: [], + disabled: [], excluded: [], actions: [{ value: 'selectAll', @@ -58,7 +59,7 @@ define([ initObservable: function () { this._super() .observe([ - 'menuVisible', + 'disabled', 'selected', 'excluded', 'excludeMode', @@ -72,28 +73,6 @@ define([ return this; }, - /** - * Toggles menu with a list of select actions. - * - * @returns {Multiselect} Chainable. - */ - toggleMenu: function () { - this.menuVisible(!this.menuVisible()); - - return this; - }, - - /** - * Hides menu with a list of select actions. - * - * @returns {Multiselect} Chainable. - */ - hideMenu: function () { - this.menuVisible(false); - - return this; - }, - /** * Selects specified record. * @@ -216,7 +195,10 @@ define([ * @returns {Multiselect} Chainable. */ selectPage: function () { - var selected = _.union(this.selected(), this.getIds()); + var selected = _.difference( + _.union(this.selected(), this.getIds()), + this.disabled() + ); this.selected(selected); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js index 46cbff1cf1dfd610ea5a3cd016304493cf880fde..f55a2efdd6311ec0078830eb1a50d37dc82bb3a5 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/select.js @@ -11,24 +11,24 @@ define([ /** * Retrieves label associated with a provided value. * - * @param {(String|Number)} value - Value of the option. + * @param {Array} values - Values of the option. * @returns {String} */ - getLabel: function (value) { + getLabel: function (values) { var options = this.options || [], - label = ''; + labels = []; - value = value || ''; + values = values || []; /*eslint-disable eqeqeq*/ - options.some(function (item) { - label = item.label; - - return item.value == value; + options.forEach(function (item) { + if(values.indexOf(item.value) > -1) { + labels.push(item.label); + } }); /*eslint-enable eqeqeq*/ - return label; + return labels.join(', '); } }); }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js b/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js index a684f5eece069704ff3361ef02eb43c7d98f6a04..8bd34bb57f6a29a6413a6405275506ca6fee1db8 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js @@ -5,10 +5,9 @@ define([ 'underscore', 'mageUtils', - 'uiRegistry', 'uiLayout', - 'Magento_Ui/js/lib/collapsible' -], function (_, utils, registry, layout, Collapsible) { + 'uiComponent' +], function (_, utils, layout, Component) { 'use strict'; /** @@ -27,7 +26,7 @@ define([ return path.join('.'); } - return Collapsible.extend({ + return Component.extend({ defaults: { template: 'ui/grid/controls/bookmarks/bookmarks', defaultIndex: 'default', diff --git a/app/code/Magento/Ui/view/base/web/js/grid/controls/columns.js b/app/code/Magento/Ui/view/base/web/js/grid/controls/columns.js index 765e437334285090378e1db15d8ac33753da9ab8..4673bedd251f7a8b3ac778b40985bff8ed7a5eb0 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/controls/columns.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/controls/columns.js @@ -6,11 +6,11 @@ define([ 'underscore', 'mageUtils', 'mage/translate', - 'Magento_Ui/js/lib/collapsible' -], function (_, utils, $t, Collapsible) { + 'uiComponent' +], function (_, utils, $t, Component) { 'use strict'; - return Collapsible.extend({ + return Component.extend({ defaults: { template: 'ui/grid/controls/columns', minVisible: 1, diff --git a/app/code/Magento/Ui/view/base/web/js/grid/dnd.js b/app/code/Magento/Ui/view/base/web/js/grid/dnd.js index 3fa06634bd42c2a69c9001ec5b559dbdc1317c16..fbcffc466f1d01583e1b470799550a8afa78e7fe 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/dnd.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/dnd.js @@ -78,6 +78,7 @@ define([ y >= area.top && y <= area.bottom ); } + /*eslint-enable no-extra-parens*/ /** @@ -109,10 +110,22 @@ define([ return ko.dataFor(elem); } + /** + * Checks whether cols are identical + * + * @param {HTMLElement} c1 + * @param {HTMLElement} c2 + * @returns {Boolean} + */ + function compareCols(c1, c2) { + return c1.cellIndex === c2.cellIndex; + } + return Class.extend({ defaults: { rootSelector: '${ $.columnsProvider }:.admin__data-grid-wrap', tableSelector: '${ $.rootSelector } -> table.data-grid', + mainTableSelector: '[data-role="grid"]', columnSelector: '${ $.tableSelector } thead tr th', noSelectClass: '_no-select', hiddenClass: '_hidden', @@ -178,7 +191,7 @@ define([ * @returns {Dnd} Chainable. */ initTable: function (table) { - this.table = table; + this.table = $(table).is(this.mainTableSelector) ? table : this.table; $(table).addClass('data-grid-draggable'); @@ -263,12 +276,15 @@ define([ rect; this.coords = this.columns.map(function (column) { - var data; + var data, + colIndex = _.findIndex(cells, function (cell) { + return compareCols(cell, column); + }); rect = column.getBoundingClientRect(); data = { - index: cells.indexOf(column), + index: colIndex, target: column, orig: rect, left: rect.left - bodyRect.left, @@ -372,7 +388,7 @@ define([ this.dragleave(leavedArea); } - if (area && area.target !== this.dragArea.target) { + if (area && !compareCols(area.target, this.dragArea.target)) { this.dragenter(area); } }, @@ -479,7 +495,7 @@ define([ getModel(dragElem).dragging(false); - if (dropArea && dropArea.target !== dragElem) { + if (dropArea && !compareCols(dropArea.target, dragElem)) { this.drop(dropArea, dragArea); } }, diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor-view.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor-view.js index aab27ca0645e1f2246e5fdc07bf49096d9c846f5..9bd9d5318fef5db2c341e98c18abd6ad7bf211bc 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor-view.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor-view.js @@ -96,8 +96,10 @@ define([ * @returns {View} Chainable. */ initBulk: function (table) { + var tableBody = $('tbody', table)[0]; + $(this.bulkTmpl) - .prependTo('tbody', table) + .prependTo(tableBody) .applyBindings(this.model); return this; diff --git a/app/code/Magento/Ui/view/base/web/js/grid/export.js b/app/code/Magento/Ui/view/base/web/js/grid/export.js index 0350d156f2656ec8316d7152437a48ca93729dc5..032f5fcc36717432c9a8c0f08a41d7f9c42f5d13 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/export.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/export.js @@ -5,12 +5,11 @@ define([ 'jquery', 'underscore', - 'ko', - 'Magento_Ui/js/lib/collapsible' -], function ($, _, ko, Collapsible) { + 'uiComponent' +], function ($, _, Component) { 'use strict'; - return Collapsible.extend({ + return Component.extend({ defaults: { template: 'ui/grid/exportButton', diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/chips.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/chips.js index 5af83c6ddf76f96ee751b7895c787a763ecdb588..a31b1fd2d50ad971f39ad8f4644d7bdb361cd095 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/chips.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/chips.js @@ -9,7 +9,8 @@ define([ return Component.extend({ defaults: { - template: 'ui/grid/filters/chips' + template: 'ui/grid/filters/chips', + stickyTmpl: 'ui/grid/sticky/chips' }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js index b0eca130b00dfef94b2da7f276724771b61d01a0..e90dfd5984189c933635261bcfbf9d17da9dcbe8 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js @@ -6,8 +6,8 @@ define([ 'underscore', 'mageUtils', 'uiLayout', - 'Magento_Ui/js/lib/collapsible' -], function (_, utils, layout, Collapsible) { + 'uiComponent' +], function (_, utils, layout, Component) { 'use strict'; /** @@ -34,9 +34,10 @@ define([ return utils.mapRecursive(data, utils.removeEmptyValues.bind(utils)); } - return Collapsible.extend({ + return Component.extend({ defaults: { template: 'ui/grid/filters/filters', + stickyTmpl: 'ui/grid/sticky/filters', applied: { placeholder: true }, @@ -157,15 +158,6 @@ define([ return this; }, - /** - * Tells wether filters pannel should be opened. - * - * @returns {Boolean} - */ - isOpened: function () { - return this.opened() && this.hasVisible(); - }, - /** * Tells wether specified filter should be visible. * diff --git a/app/code/Magento/Ui/view/base/web/js/grid/listing.js b/app/code/Magento/Ui/view/base/web/js/grid/listing.js index 2713816a1686ac77edcca7a407fa61ff4a3a5841..7ec985fcf76a53dc540a4cd020b5a339b8e9065e 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/listing.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/listing.js @@ -2,6 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ + define([ 'underscore', 'Magento_Ui/js/lib/spinner', @@ -13,6 +14,7 @@ define([ return Component.extend({ defaults: { template: 'ui/grid/listing', + stickyTmpl: 'ui/grid/sticky/listing', positions: false, storageConfig: { positions: '${ $.storageConfig.path }.positions' @@ -103,6 +105,11 @@ define([ return this; }, + /** + * Creates resize widget instance. + * + * @returns {Listing} Chainable. + */ initResize: function () { layout([this.resizeConfig]); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/massactions.js b/app/code/Magento/Ui/view/base/web/js/grid/massactions.js index 871bbfbffc1624b312d6006ccf4b38599c5abb04..2e9a39b2bddd261c83c889ff1f31de99836b9544 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/massactions.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/massactions.js @@ -2,6 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ + define([ 'underscore', 'uiRegistry', @@ -16,6 +17,8 @@ define([ return Collapsible.extend({ defaults: { template: 'ui/grid/actions', + stickyTmpl: 'ui/grid/sticky/actions', + componentType: 'massaction', selectProvider: '', actions: [], noItemsMsg: $t('You haven\'t selected any items!'), diff --git a/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js b/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js index d1d61eca2b76bf231a0667d88da125f153977851..3f8c6cd8a658c3d9f660897434d1ca026033247a 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js @@ -2,6 +2,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ + define([ 'ko', 'underscore', @@ -14,9 +15,11 @@ define([ return Component.extend({ defaults: { template: 'ui/grid/paging/paging', + totalTmpl: 'ui/grid/paging-total', pageSize: 20, current: 1, selectProvider: '', + componentType: 'paging', sizesConfig: { component: 'Magento_Ui/js/grid/paging/sizes', diff --git a/app/code/Magento/Ui/view/base/web/js/grid/paging/sizes.js b/app/code/Magento/Ui/view/base/web/js/grid/paging/sizes.js index 177d1c6afe0a3b8e96f71c4e2339b27ca8f7df8e..62908da372266e90bba65a6f5c8dd368d42129c9 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/paging/sizes.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/paging/sizes.js @@ -6,11 +6,11 @@ define([ 'ko', 'underscore', 'mageUtils', - 'Magento_Ui/js/lib/collapsible' -], function (ko, _, utils, Collapsible) { + 'uiComponent' +], function (ko, _, utils, Component) { 'use strict'; - return Collapsible.extend({ + return Component.extend({ defaults: { template: 'ui/grid/paging/sizes', value: 20, @@ -392,8 +392,8 @@ define([ * Listener of the 'value' property changes. */ onValueChange: function () { - this.close() - .discardAll(); + this.discardAll() + .trigger('close'); }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/grid/resize.js b/app/code/Magento/Ui/view/base/web/js/grid/resize.js index a98dd05073ba2303d37f67167d3b2e1d37baa623..cef9cb707cf7d064cea50b21c89142009fb0c087 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/resize.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/resize.js @@ -18,6 +18,7 @@ define([ defaults: { rootSelector: '${ $.columnsProvider }:.admin__data-grid-wrap', tableSelector: '${ $.rootSelector } -> table.data-grid', + mainTableSelector: '[data-role="grid"]', columnSelector: '${ $.tableSelector } thead tr th', fieldSelector: '${ $.tableSelector } tbody tr td', @@ -84,10 +85,15 @@ define([ * @returns {Object} Chainable */ initTable: function (table) { - this.table = table; - this.tableWidth = $(table).outerWidth(); + if ($(table).is(this.mainTableSelector)) + { + this.table = table; + this.tableWidth = $(table).outerWidth(); + $(window).resize(this.checkAfterResize); + } + $(table).addClass(this.fixedLayoutClass); - $(window).resize(this.checkAfterResize); + return this; }, @@ -202,15 +208,17 @@ define([ */ initColumn: function (column) { var model = ko.dataFor(column), + ctxIndex = this.getCtxIndex(ko.contextFor(column)), table = this.table; model.width = this.getDefaultWidth(column); - if (!this.hasColumn(model, false)) { + if (!this.hasColumn(model, ctxIndex, false)) { + this.columnsElements[model.index] = this.columnsElements[model.index] || {}; + this.columnsElements[model.index][ctxIndex] = column; this.initResizableElement(column); - this.columnsElements[model.index] = column; - $(column).outerWidth(model.width); this.setStopPropagationHandler(column); + $(column).outerWidth(model.width); } this.refreshLastColumn(column); @@ -383,7 +391,7 @@ define([ _canResize: function (column) { if ( $(column).hasClass(this.visibleClass) || - !$(this.resizeConfig.depResizeElem.elem).find('.' + this.resizableElementClass).length + !$(this.resizeConfig.depResizeElem.elems[0]).find('.' + this.resizableElementClass).length ) { return false; } @@ -406,11 +414,11 @@ define([ event.stopImmediatePropagation(); cfg.curResizeElem.model = ko.dataFor(column); cfg.curResizeElem.ctx = ko.contextFor(column); - cfg.curResizeElem.elem = this.hasColumn(cfg.curResizeElem.model, true); + cfg.curResizeElem.elems = this.hasColumn(cfg.curResizeElem.model, false, true); cfg.curResizeElem.position = event.pageX; - cfg.depResizeElem.elem = this.getNextElement(cfg.curResizeElem.elem); - cfg.depResizeElem.model = ko.dataFor(cfg.depResizeElem.elem); - cfg.depResizeElem.ctx = ko.contextFor(cfg.depResizeElem.elem); + cfg.depResizeElem.elems = this.getNextElements(cfg.curResizeElem.elems[0]); + cfg.depResizeElem.model = ko.dataFor(cfg.depResizeElem.elems[0]); + cfg.depResizeElem.ctx = ko.contextFor(cfg.depResizeElem.elems[0]); this._setResizeClass(); @@ -420,8 +428,8 @@ define([ event.stopPropagation(); this.resizable = true; - cfg.curResizeElem.model.width = $(cfg.curResizeElem.elem).outerWidth(); - cfg.depResizeElem.model.width = $(cfg.depResizeElem.elem).outerWidth(); + cfg.curResizeElem.model.width = $(cfg.curResizeElem.elems[0]).outerWidth(); + cfg.depResizeElem.model.width = $(cfg.depResizeElem.elems[0]).outerWidth(); body.addClass(this.inResizeClass); body.bind('mousemove', this.mousemoveHandler); $(window).bind('mouseup', this.mouseupHandler); @@ -435,7 +443,8 @@ define([ */ mousemoveHandler: function (event) { var cfg = this.resizeConfig, - width = event.pageX - cfg.curResizeElem.position; + width = event.pageX - cfg.curResizeElem.position, + self = this; event.stopPropagation(); event.preventDefault(); @@ -448,26 +457,40 @@ define([ ) { cfg.curResizeElem.model.width += width; cfg.depResizeElem.model.width -= width; - $(cfg.curResizeElem.elem).outerWidth(cfg.curResizeElem.model.width); - $(cfg.depResizeElem.elem).outerWidth(cfg.depResizeElem.model.width); + + cfg.curResizeElem.elems.forEach(function (el) { + $(el).outerWidth(cfg.curResizeElem.model.width); + }); + cfg.depResizeElem.elems.forEach(function (el) { + $(el).outerWidth(cfg.depResizeElem.model.width); + }); + cfg.previousWidth = width; cfg.curResizeElem.position = event.pageX; } else if (width <= -(cfg.curResizeElem.model.width - this.minColumnWidth)) { - $(cfg.curResizeElem.elem).outerWidth(this.minColumnWidth); - $(cfg.depResizeElem.elem).outerWidth( + cfg.curResizeElem.elems.forEach(function (el) { + $(el).outerWidth(self.minColumnWidth); + }); + cfg.depResizeElem.elems.forEach(function (el) { + $(el).outerWidth( cfg.depResizeElem.model.width + cfg.curResizeElem.model.width - - this.minColumnWidth - ); + self.minColumnWidth); + }); + } else if (width >= cfg.depResizeElem.model.width - this.minColumnWidth) { - $(cfg.depResizeElem.elem).outerWidth(this.minColumnWidth); - $(cfg.curResizeElem.elem).outerWidth( - cfg.curResizeElem.model.width + - cfg.depResizeElem.model.width - - this.minColumnWidth - ); + cfg.depResizeElem.elems.forEach(function (el) { + $(el).outerWidth(self.minColumnWidth); + }); + cfg.curResizeElem.elems.forEach(function (el) { + $(el).outerWidth( + cfg.curResizeElem.model.width + + cfg.depResizeElem.model.width - + self.minColumnWidth + ); + }); } }, @@ -502,17 +525,17 @@ define([ * @param {Object} element - current element * @returns {Object} next element data */ - getNextElement: function (element) { + getNextElements: function (element) { var nextElem = $(element).next()[0], nextElemModel = ko.dataFor(nextElem), - nextElemData = this.hasColumn(nextElemModel, true); + nextElemData = this.hasColumn(nextElemModel, false, true); if (nextElemData) { if (nextElemModel.visible()) { return nextElemData; } - return this.getNextElement(nextElem); + return this.getNextElements(nextElem); } }, @@ -540,14 +563,20 @@ define([ * Check column is render or not * * @param {Object} model - cur column model + * @param {String|Boolean} ctxIndex - index of context, or false, if want to get cols from all ctx * @param {Boolean} returned - need return column object or not - * @return {Boolean} if returned param is false, returned boolean falue, else return current object data + * @return {Boolean} if returned param is false, returned boolean value, else return current object data */ - hasColumn: function (model, returned) { - if (this.columnsElements.hasOwnProperty(model.index)) { + hasColumn: function (model, ctxIndex, returned) { + var colElem = this.columnsElements[model.index] || {}, + getFromAllCtx = ctxIndex === false; + + if (colElem && (getFromAllCtx || colElem.hasOwnProperty(ctxIndex))) { if (returned) { - return this.columnsElements[model.index]; + return getFromAllCtx ? + _.values(colElem) : + colElem[ctxIndex]; } return true; @@ -581,6 +610,19 @@ define([ } return false; + }, + + /** + * Generate index that will indentify context + * + * @param {Object} ctx + * @return {String} + */ + getCtxIndex: function (ctx) + { + return ctx ? ctx.$parents.reduce(function (pv, cv) { + return (pv.index || pv) + (cv || {}).index; + }) : ctx; } }); }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/sticky/sticky.js b/app/code/Magento/Ui/view/base/web/js/grid/sticky/sticky.js new file mode 100644 index 0000000000000000000000000000000000000000..e779851a9315625ebe3b60f5c09c8769098139d1 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/grid/sticky/sticky.js @@ -0,0 +1,577 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/lib/view/utils/async', + 'underscore', + 'uiComponent', + 'Magento_Ui/js/lib/view/utils/raf' +], function ($, _, Component, raf) { + 'use strict'; + + return Component.extend({ + defaults: { + listingSelector: '${ $.listingProvider }::not([data-role = "sticky-el-root"])', + toolbarSelector: '${ $.toolbarProvider }::not([data-role = "sticky-el-root"])', + bulkRowSelector: '[data-role = "data-grid-bulk-row"]', + bulkRowHeaderSelector: '.data-grid-info-panel:visible', + tableSelector: 'table', + columnSelector: 'thead tr th', + rowSelector: 'tbody tr', + toolbarCollapsiblesSelector: '[data-role="toolbar-menu-item"]', + toolbarCollapsiblesActiveClass: '_active', + template: 'ui/grid/sticky/sticky', + stickyContainerSelector: '.sticky-header', + stickyElementSelector: '[data-role = "sticky-el-root"]', + leftDataGridCapSelector: '.data-grid-cap-left', + rightDataGridCapSelector: '.data-grid-cap-right', + visible: false, + enableToolbar: true, + enableHeader: true, + modules: { + toolbar: '${ $.toolbarProvider }', + listing: '${ $.listingProvider }' + }, + otherStickyElsSize: 77, + containerNode: null, + listingNode: null, + toolbarNode: null, + stickyListingNode: null, + stickyToolbarNode: null, + storedOriginalToolbarElements: [], + cache: {}, + flags: {}, + dirtyFlag: 'dirty' + }, + + /** + * Initializes Sticky component. + * + * @returns {Object} Chainable. + */ + initialize: function () { + this._super(); + _.bindAll(this, + 'adjustStickyElems', + 'initListingNode', + 'initToolbarNode', + 'initContainerNode', + 'initStickyListingNode', + 'initStickyToolbarNode', + 'initLeftDataGridCap', + 'initRightDataGridCap' + ); + + $.async(this.listingSelector, + this.initListingNode); + $.async(this.toolbarSelector, + this.initToolbarNode); + + $.async(this.stickyContainerSelector, + this, + this.initContainerNode); + $.async(this.stickyElementSelector, + this.listing(), + this.initStickyListingNode); + $.async(this.stickyElementSelector, + this.toolbar(), + this.initStickyToolbarNode); + + return this; + }, + + /** + * Init observables + * + * @returns {Object} Chainable. + */ + initObservable: function () { + this._super() + .observe(['visible']); + + return this; + }, + + /** + * Init original listing node + * + * @param {HTMLElement} node + */ + initListingNode: function (node) { + if ($(node).is(this.stickyElementSelector)) { + return; + } + this.listingNode = $(node); + this.columns = this.listingNode.find(this.columnSelector); + }, + + /** + * Init original toolbar node + * + * @param {HTMLElement} node + */ + initToolbarNode: function (node) { + if ($(node).is(this.stickyElementSelector)) { + return; + } + this.toolbarNode = $(node); + }, + + /** + * Init sticky listing node + * + * @param {HTMLElement} node + */ + initStickyListingNode: function (node) { + this.stickyListingNode = $(node); + this.checkPos(); + this.initListeners(); + }, + + /** + * Init sticky toolbar node + * + * @param {HTMLElement} node + */ + initStickyToolbarNode: function (node) { + this.stickyToolbarNode = $(node); + }, + + /** + * Init sticky header container node + * + * @param {HTMLElement} node + */ + initContainerNode: function (node) { + this.containerNode = $(node); + + $.async(this.leftDataGridCapSelector, + node, + this.initLeftDataGridCap); + $.async(this.rightDataGridCapSelector, + node, + this.initRightDataGridCap); + }, + + /** + * Init left DataGridCap + * + * @param {HTMLElement} node + */ + initLeftDataGridCap: function (node) { + this.leftDataGridCap = $(node); + }, + + /** + * Init right DataGridCap + * + * @param {HTMLElement} node + */ + initRightDataGridCap: function (node) { + this.rightDataGridCap = $(node); + }, + + /** + * Init listeners + * + * @returns {Object} Chainable. + */ + initListeners: function () { + this.adjustStickyElems(); + this.initOnResize() + .initOnScroll() + .initOnListingScroll(); + + return this; + }, + + /** + * Start to listen to window scroll event + * + * @returns {Object} Chainable. + */ + initOnScroll: function () { + this.lastHorizontalScrollPos = $(window).scrollLeft(); + document.addEventListener('scroll', function () { + this.flags.scrolled = true; + }.bind(this)); + + return this; + }, + + /** + * Start to listen to original listing scroll event + * + * @returns {Object} Chainable. + */ + initOnListingScroll: function () { + $(this.listingNode).scroll(function (e) { + this.flags.listingScrolled = true; + this.flags.listingScrolledValue = $(e.target).scrollLeft(); + }.bind(this)); + + return this; + }, + + /** + * Start to listen to window resize event + * + * @returns {Object} Chainable. + */ + initOnResize: function () { + $(window).resize(function () { + this.flags.resized = true; + }.bind(this)); + + return this; + }, + + /** + * Adjust sticky header elements according to flags of the events that have happened in the endless RAF loop + */ + adjustStickyElems: function () { + if (this.flags.resized || + this.flags.scrolled) { + this.checkPos(); + } + + if (this.visible()) { + this.checkTableElemsWidth(); + + if (this.flags.originalWidthChanged) { + this.adjustContainerElemsWidth(); + } + + if (this.flags.resized) { + this.onResize(); + } + + if (this.flags.scrolled) { + this.onWindowScroll(); + } + + if (this.flags.listingScrolled) { + this.onListingScroll(this.flags.listingScrolledValue); + } + } + _.each(this.flags, function (val, key) { + if (val === this.dirtyFlag) { + this.flags[key] = false; + } else if (val) { + this.flags[key] = this.dirtyFlag; + } + }, this); + + raf(this.adjustStickyElems); + }, + + /** + * Handles window scroll + */ + onWindowScroll: function () { + var scrolled = $(window).scrollLeft(), + horizontal = this.lastHorizontalScrollPos !== scrolled; + + if (horizontal) { + this.adjustOffset() + .adjustDataGridCapPositions(); + this.lastHorizontalScrollPos = scrolled; + } else { + this.checkPos(); + } + }, + + /** + * Handles original listing scroll + * + * @param {Number} scrolled + */ + onListingScroll: function (scrolled) { + this.adjustOffset(scrolled); + }, + + /** + * Handles window resize + */ + onResize: function () { + this.checkPos(); + this.adjustContainerElemsWidth() + .adjustDataGridCapPositions(); + }, + + /** + * Check if original table or columns change it dimensions and sets appropriate flag + */ + checkTableElemsWidth: function () { + var newWidth = this.getTableWidth(); + + if (this.cache.tableWidth !== newWidth) { + this.cache.tableWidth = newWidth; + this.flags.originalWidthChanged = true; + } else if (this.cache.colChecksum !== this.getColsChecksum()) { + this.cache.colChecksum = this.getColsChecksum(); + this.flags.originalWidthChanged = true; + } + }, + + /** + * Get the checksum of original columns width + * + * @returns {Number}. + */ + getColsChecksum: function () { + return _.reduce(this.columns, + function (pv, cv) { + return ($(pv).width() || pv) + '' + $(cv).width(); + }); + }, + + /** + * Get the width of the sticky table wrapper + * + * @returns {Number}. + */ + getListingWidth: function () { + return this.listingNode.width(); + }, + + /** + * Get the width of the original table + * + * @returns {Number}. + */ + getTableWidth: function () { + return this.listingNode.find(this.tableSelector).width(); + }, + + /** + * Get the top elem: header or toolbar + * + * @returns {HTMLElement}. + */ + getTopElement: function () { + return this.toolbarNode || this.listingNode; + }, + + /** + * Get the height of the other sticky elem (Page header) + * + * @returns {Number}. + */ + getOtherStickyElementsSize: function () { + return this.otherStickyElsSize; + }, + + /** + * Get top Y coord of the sticky header + * + * @returns {Number}. + */ + getListingTopYCoord: function () { + var bulkRowHeight = (this.listingNode.find(this.bulkRowSelector) || {}).height(); + + return this.listingNode.find('tbody').offset().top - + this.containerNode.height() - + $(window).scrollTop() + + bulkRowHeight; + }, + + /** + * Check if sticky header must be visible + * + * @returns {Boolean}. + */ + getMustBeSticky: function () { + var stickyTopCondition = this.getListingTopYCoord() - this.getOtherStickyElementsSize(), + stickyBottomCondition = this.listingNode.offset().top + + this.listingNode.height() - + $(window).scrollTop() - + (this.listingNode.find(this.bulkRowSelector) || {}).height() - + this.getOtherStickyElementsSize(); + + return stickyTopCondition < 0 && stickyBottomCondition > 0; + }, + + /** + * Resize sticky header and cols + * + * @returns {Object} Chainable. + */ + adjustContainerElemsWidth: function () { + this.resizeContainer() + .resizeCols() + .resizeBulk(); + + return this; + }, + + /** + * Resize sticky header + * + * @returns {Object} Chainable. + */ + resizeContainer: function () { + var listingWidth = this.getListingWidth(); + + this.stickyListingNode.innerWidth(listingWidth); + this.stickyListingNode.find(this.tableSelector).innerWidth(this.getTableWidth()); + + if (this.stickyToolbarNode) { + this.stickyToolbarNode.innerWidth(listingWidth); + } + + return this; + }, + + /** + * Resize sticky cols + * + * @returns {Object} Chainable. + */ + resizeCols: function () { + var cols = this.listingNode.find(this.columnSelector); + + this.stickyListingNode.find(this.columnSelector).each(function (ind) { + var originalColWidth = $(cols[ind]).width(); + + $(this).width(originalColWidth); + }); + + return this; + }, + + /** + * Resize bulk row header + * + * @returns {Object} Chainable. + */ + resizeBulk: function () { + var bulk = this.containerNode.find(this.bulkRowHeaderSelector)[0]; + if (bulk){ + $(bulk).innerWidth(this.getListingWidth()); + } + }, + + /** + * Reset viewport to the top of listing + */ + resetToTop: function () { + var posOfTopEl = this.getTopElement().offset().top - this.getOtherStickyElementsSize() || 0; + + $(window).scrollTop(posOfTopEl); + }, + + /** + * Adjust sticky header offset + * + * @param {Number} val + * @returns {Object} Chainable. + */ + adjustOffset: function (val) { + val = val || this.listingNode.scrollLeft(); + this.stickyListingNode.offset({ + left: this.listingNode.offset().left - val + }); + + return this; + }, + + /** + * Adjust both DataGridCap position + * + * @returns {Object} Chainable. + */ + adjustDataGridCapPositions: function () { + this.adjustLeftDataGridCapPos() + .adjustRightDataGridCapPos(); + + return this; + }, + + /** + * Adjust left DataGridCap position + * + * @returns {Object} Chainable. + */ + adjustLeftDataGridCapPos: function () { + this.leftDataGridCap.offset({ + left: this.listingNode.offset().left - this.leftDataGridCap.width() + }); + + return this; + }, + + /** + * Adjust right DataGridCap position + * + * @returns {Object} Chainable. + */ + adjustRightDataGridCapPos: function () { + this.rightDataGridCap.offset({ + left: this.listingNode.offset().left + this.listingNode.width() + }); + + return this; + }, + + /** + * Hides the oiginal toolbar opened dropdowns/collapsibles etc + */ + collapseOriginalElements: function () { + this.toolbarNode + .find(this.toolbarCollapsiblesSelector) + .css('visibility', 'hidden'); + $(this.listingNode.find(this.bulkRowSelector)[0]).css('visibility', 'hidden'); + }, + + /** + * Restores the oiginal toolbar opened dropdowns/collapsibles etc + */ + restoreOriginalElements: function () { + this.toolbarNode + .find(this.toolbarCollapsiblesSelector) + .css('visibility', 'visible'); + $(this.listingNode.find(this.bulkRowSelector)[0]).css('visibility', 'visible'); + }, + + /** + * Toggle the visibility of sticky header + * + * @returns {Object} Chainable. + */ + toggleContainerVisibility: function () { + this.visible(!this.visible()); + + return this; + }, + + /** + * Checks position of the listing to know if need to show/hide sticky header + * + * @returns {Boolean} whether the visibility of the sticky header was toggled. + */ + checkPos: function () { + var isSticky = this.visible(), + mustBeSticky = this.getMustBeSticky(), + needChange = isSticky !== mustBeSticky; + + if (needChange) { + if (mustBeSticky) { + this.collapseOriginalElements(); + this.toggleContainerVisibility(); + this.adjustContainerElemsWidth() + .adjustOffset() + .adjustDataGridCapPositions(); + + } else { + this.toggleContainerVisibility(); + this.restoreOriginalElements(); + } + } + + return needChange; + } + }); +}); diff --git a/app/code/Magento/Ui/view/base/web/js/lib/component/core.js b/app/code/Magento/Ui/view/base/web/js/lib/component/core.js index fd3e81680aee113c56769ffd17786fc684a5159a..65eca7b967d7a31cb7ad750cd213ee051ad0898c 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/component/core.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/component/core.js @@ -23,7 +23,8 @@ define([ }, modules: { storage: '${ $.storageConfig.provider }' - } + }, + componentType: 'container' }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/collapsible.js b/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/collapsible.js new file mode 100644 index 0000000000000000000000000000000000000000..0c5686fa8936ada9151c3c657b1ad0036e08401d --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/collapsible.js @@ -0,0 +1,189 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'ko', + 'jquery', + 'underscore' +], function (ko, $, _) { + 'use strict'; + + var collapsible, + defaults; + + defaults = { + closeOnOuter: true, + onTarget: false, + openClass: '_active', + as: '$collapsible' + }; + + collapsible = { + + /** + * Sets 'opened' property to true. + */ + open: function () { + this.opened(true); + }, + + /** + * Sets 'opened' property to false. + */ + close: function () { + this.opened(false); + }, + + /** + * Toggles value of the 'opened' property. + */ + toggle: function () { + this.opened(!this.opened()); + } + }; + + /** + * Document click handler which in case if event target is not + * a descendant of provided container element, closes collapsible model. + * + * @param {HTMLElement} container + * @param {Object} model + * @param {EventObject} e + */ + function onOuterClick(container, model, e) { + var target = e.target; + + if (target !== container && !container.contains(target)) { + model.close(); + } + } + + /** + * Creates 'css' binding which toggles + * class specified in 'name' parameter. + * + * @param {Object} model + * @param {String} name + * @returns {Object} + */ + function getClassBinding(model, name) { + var binding = {}; + + binding[name] = model.opened; + + return { + css: binding + }; + } + + /** + * Prepares configuration for the binding based + * on a default properties and provided options. + * + * @param {Object} [options={}] + * @returns {Object} Complete instance configuration. + */ + function buildConfig(options) { + if (typeof options !== 'object') { + options = {}; + } + + return _.extend({}, defaults, options); + } + + ko.bindingHandlers.collapsible = { + + /** + * Initializes 'collapsible' binding. + */ + init: function (element, valueAccessor, allBindings, viewModel, bindingCtx) { + var $collapsible = Object.create(collapsible), + config = buildConfig(valueAccessor()), + outerClick, + bindings; + + _.bindAll($collapsible, 'open', 'close', 'toggle'); + + $collapsible.opened = ko.observable(false); + + bindingCtx[config.as] = $collapsible; + + if (config.closeOnOuter) { + outerClick = onOuterClick.bind(null, element, $collapsible); + + $(document).on('click', outerClick); + + ko.utils.domNodeDisposal.addDisposeCallback(element, function () { + $(document).off('click', outerClick); + }); + } + + if (config.openClass) { + bindings = getClassBinding($collapsible, config.openClass); + + ko.applyBindingsToNode(element, bindings, bindingCtx); + } + + if (config.onTarget) { + $(element).on('click', $collapsible.toggle); + } + + if (viewModel && _.isFunction(viewModel.on)) { + viewModel.on({ + close: $collapsible.close, + open: $collapsible.open, + toggleOpened: $collapsible.toggle + }); + } + } + }; + + ko.bindingHandlers.closeCollapsible = { + + /** + * Creates listener for the click event on provided DOM element, + * which closes associated with it collapsible model. + */ + init: function (element, valueAccessor, allBindings, viewModel, bindingCtx) { + var name = valueAccessor() || defaults.as, + $collapsible = bindingCtx[name]; + + if ($collapsible) { + $(element).on('click', $collapsible.close); + } + } + }; + + ko.bindingHandlers.openCollapsible = { + + /** + * Creates listener for the click event on provided DOM element, + * which opens associated with it collapsible model. + */ + init: function (element, valueAccessor, allBindings, viewModel, bindingCtx) { + var name = valueAccessor() || defaults.as, + $collapsible = bindingCtx[name]; + + if ($collapsible) { + $(element).on('click', $collapsible.open); + } + } + }; + + ko.bindingHandlers.toggleCollapsible = { + + /** + * Creates listener for the click event on provided DOM element, + * which toggles associated with it collapsible model. + */ + init: function (element, valueAccessor, allBindings, viewModel, bindingCtx) { + var name = valueAccessor() || defaults.as, + $collapsible = bindingCtx[name]; + + if ($collapsible) { + $(element).on('click', $collapsible.toggle); + } + } + }; +}); diff --git a/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/optgroup.js b/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/optgroup.js index b4ef5df5a1340ed510a92e0378953cc1a5fa09ce..b95709baf311418d8a362f9a7ad86f1f40c389fb 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/optgroup.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/optgroup.js @@ -67,8 +67,13 @@ define([ if (typeof unwrappedArray.length === 'undefined') { // Coerce single value into array unwrappedArray = [unwrappedArray]; } + // Filter out any entries marked as destroyed filteredArray = ko.utils.arrayFilter(unwrappedArray, function (item) { + if (item && !item.label) { + return false; + } + return includeDestroyed || item === undefined || item === null || !ko.utils.unwrapObservable(item._destroy); }); filteredArray.map(recursivePathBuilder, null); @@ -211,8 +216,8 @@ define([ // IE6 doesn't like us to assign selection to OPTION nodes before they're added to the document. // That's why we first added them without selection. Now it's time to set the selection. if (previousSelectedValues.length) { - var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions[0])) >= 0; - ko.utils.setOptionNodeSelectionState(newOptions[0], isSelected); + var isSelected = ko.utils.arrayIndexOf(previousSelectedValues, ko.selectExtensions.readValue(newOptions.value)) >= 0; + ko.utils.setOptionNodeSelectionState(newOptions.value, isSelected); // If this option was changed from being selected during a single-item update, notify the change if (itemUpdate && !isSelected) { diff --git a/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/outer_click.js b/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/outer_click.js index 843c126b663413058238b86dc03e5f82d2622f14..2c0bfa635587ef1c586d0f2dd9f4c8ab36ba48ed 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/outer_click.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/ko/bind/outer_click.js @@ -5,38 +5,75 @@ /** Creates outerClick binding and registers in to ko.bindingHandlers object */ define([ 'ko', - 'jquery' -], function (ko, $) { + 'jquery', + 'underscore' +], function (ko, $, _) { 'use strict'; - function clickWrapper(elem, callback, e) { - var target = e.target; + var defaults = { + onlyIfVisible: true + }; + + /** + * Document click handler which in case if event target is not + * a descendant of provided container element, + * invokes specfied in configuration callback. + * + * @param {HTMLElement} container + * @param {Object} config + * @param {EventObject} e + */ + function onOuterClick(container, config, e) { + var target = e.target, + callback = config.callback; - if (target !== elem && !elem.contains(target)) { + if (container === target || container.contains(target)) { + return; + } + + if (config.onlyIfVisible) { + if (!_.isNull(container.offsetParent)) { + callback(); + } + } else { callback(); } } + /** + * Prepares configuration for the binding based + * on a default properties and provided options. + * + * @param {(Object|Function)} [options={}] + * @returns {Object} + */ + function buildConfig(options) { + var config = {}; + + if (_.isFunction(options)) { + options = { + callback: options + }; + } else if (!_.isObject(options)) { + options = {}; + } + + return _.extend(config, defaults, options); + } + ko.bindingHandlers.outerClick = { /** - * Attaches click handler to document - * @param {HTMLElement} el - Element, that binding is applied to - * @param {Function} valueAccessor - Function that returns value, passed to binding - * @param {Object} allBindings - all bindings object - * @param {Object} viewModel - reference to viewmodel + * Initializes outer click binding. */ - init: function (element, valueAccessor, allBindings, viewModel) { - var callback = valueAccessor(), - wrapper; - - callback = callback.bind(viewModel); - wrapper = clickWrapper.bind(null, element, callback); + init: function (element, valueAccessor) { + var config = buildConfig(valueAccessor()), + outerClick = onOuterClick.bind(null, element, config); - $(document).on('click', wrapper); + $(document).on('click', outerClick); ko.utils.domNodeDisposal.addDisposeCallback(element, function () { - $(document).off('click', wrapper); + $(document).off('click', outerClick); }); } }; diff --git a/app/code/Magento/Ui/view/base/web/js/lib/ko/initialize.js b/app/code/Magento/Ui/view/base/web/js/lib/ko/initialize.js index b96c6107aafed4a3058082fb9d8304a924f5d218..2b0f3d1fc5dcadfc5f630efaea6a0bf5c4379dae 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/ko/initialize.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/ko/initialize.js @@ -17,6 +17,7 @@ define([ './bind/mage-init', './bind/after-render', './bind/i18n', + './bind/collapsible', './extender/observable_array', './extender/bound-nodes', './extender/observable_array' diff --git a/app/code/Magento/Ui/view/base/web/js/lib/step-wizard.js b/app/code/Magento/Ui/view/base/web/js/lib/step-wizard.js index 020f1353c17564c29ea00b95509dd08c7abefd6d..af68997cdf95d217ccdb35590db951f0a5314233 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/step-wizard.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/step-wizard.js @@ -2,24 +2,29 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ +// jscs:disable jsDoc define([ - "uiRegistry", - "uiComponent", - "jquery", - "underscore", - "ko", - "mage/backend/notification" + 'uiRegistry', + 'uiComponent', + 'jquery', + 'underscore', + 'ko', + 'mage/backend/notification' ], function (uiRegistry, Component, $, _, ko) { - "use strict"; + 'use strict'; - ko.utils.domNodeDisposal.cleanExternalData = _.wrap(ko.utils.domNodeDisposal.cleanExternalData, - function(func, node) { + var Wizard; + + ko.utils.domNodeDisposal.cleanExternalData = _.wrap( + ko.utils.domNodeDisposal.cleanExternalData, + function (func, node) { if (!$(node).closest('[data-type=skipKO]').length) { func(node); } - }); + } + ); - var Wizard = function (steps) { + Wizard = function (steps) { this.steps = steps; this.index = 0; this.data = {}; @@ -44,19 +49,23 @@ define([ }; this.next = function () { this.move(this.index + 1); + return this.getStep().name; }; this.prev = function () { this.move(this.index - 1); + return this.getStep().name; }; - this.preventSwitch = function(newIndex) { + this.preventSwitch = function (newIndex) { return newIndex < 0 || (newIndex - this.index) > 1; }; this._next = function (newIndex) { newIndex = _.isNumber(newIndex) ? newIndex : this.index + 1; + try { this.getStep().force(this); + if (newIndex >= steps.length) { return false; } @@ -91,6 +100,7 @@ define([ this.showNotificationMessage = function () { if (!_.isEmpty(this.getStep())) { this.hideNotificationMessage(); + if (this.getStep().notificationMessage.text !== null) { this.notifyMessage( this.getStep().notificationMessage.text, @@ -109,7 +119,8 @@ define([ } }; this.setNotificationMessage = function (text, error) { - error = typeof error !== 'undefined'; + error = error !== undefined; + if (!_.isEmpty(this.getStep())) { this.getStep().notificationMessage.text = text; this.getStep().notificationMessage.error = error; @@ -189,7 +200,7 @@ define([ showSpecificStep: function () { var index = _.indexOf(this.stepsNames, event.target.hash.substr(1)), stepName = this.wizard.move(index); - + this.selectedStep(stepName); } }); diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/async.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/async.js index df89d8d435b674dd2c0d6651467687acedf2f2a4..1b5743cd9d29aea5d12841306cd3dc0c1fd4ee6f 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/async.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/async.js @@ -86,6 +86,7 @@ define([ data.ctx = ctx; } else { data.component = ctx; + data.ctx = '*'; } } else { data = _.isString(selector) ? @@ -93,8 +94,6 @@ define([ selector; } - data.ctx = data.ctx || '*'; - return data; } @@ -127,7 +126,8 @@ define([ */ function setRootListener(data, component) { boundedNodes.get(component, function (root) { - if (!$(root).is(data.ctx)) { + var ctx = data.ctx || '*' + if (!$(root).is(data.ctx || '*')) { return; } @@ -182,9 +182,11 @@ define([ domObserver.get(data.selector, data.fn, data.ctx); } }; + /*eslint-enable no-unused-vars*/ _.extend($.async, { + /*eslint-disable no-unused-vars*/ /** * Returns collection of elements found by provided selector data. @@ -215,6 +217,7 @@ define([ $(data.selector, nodes).toArray() : nodes; }, + /*eslint-enable no-unused-vars*/ /** diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js index ae5c2878e526271bf97da7521ce575a2c6547be3..25e900fe2af2994c24ae53fc1d422681bab6539c 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js @@ -42,26 +42,20 @@ define([ } /** - * Removes all non-element nodes from provided array - * and appends to it descendant elements. + * Extracts node identifier. If ID is not specified, + * then it will be created for the provided node. * - * @param {Array} nodes - * @returns {Array} + * @param {HTMLElement} node + * @returns {Number} */ - function formNodesList(nodes) { - var result = [], - children; - - _.toArray(nodes) - .filter(isElementNode) - .forEach(function (node) { - result.push(node); + function getNodeId(node) { + var id = node._observeId; - children = extractChildren(node); - result = result.concat(children); - }); + if (!id) { + id = node._observeId = counter++; + } - return result; + return id; } /** @@ -71,7 +65,15 @@ define([ * @param {Object} data */ function trigger(node, data) { + var id = getNodeId(node), + ids = data.invoked; + + if (_.contains(ids, id)) { + return; + } + data.callback(node); + data.invoked.push(id); } /** @@ -81,12 +83,8 @@ define([ * @returns {Object} */ function createNodeData(node) { - var id = node._observeId, - nodes = watchers.nodes; - - if (!id) { - id = node._observeId = counter++; - } + var nodes = watchers.nodes, + id = getNodeId(node); nodes[id] = nodes[id] || {}; @@ -137,10 +135,6 @@ define([ function addSelectorListener(selector, data) { var storage = watchers.selectors; - if (typeof selector !== 'string') { - return; - } - (storage[selector] = storage[selector] || []).push(data); } @@ -187,51 +181,67 @@ define([ } /** - * Extarcts nodes that matches specfied selector. - * If selector is an object, then it will be parsed as - * one of the possible array like values. + * Removes all non-element nodes from provided array + * and appends to it descendant elements. * - * @param {(jQueryObject|HTMLElement|Array|String)} selector - * @param {HTMLElement} [ctx=document.body] - Context that will be used to search for elements. - * @returns {Array} An array of available elements. + * @param {Array} nodes + * @returns {Array} */ - function getNodes(selector, ctx) { - var nodes = []; - - if (typeof selector === 'object') { - if (typeof selector.jquery === 'string' || !selector.tagName) { - nodes = _.toArray(selector); - } else if (selector.tagName) { - nodes = [selector]; - } - } else if (typeof selector === 'string') { - nodes = $(selector, ctx).toArray(); - } + function formNodesList(nodes) { + var result = [], + children; + + nodes = _.toArray(nodes).filter(isElementNode); - return nodes; + nodes.forEach(function (node) { + result.push(node); + + children = extractChildren(node); + result = result.concat(children); + }); + + return result; } /** - * Processes removed and added element nodes - * specified in mutation record. + * Collects all removed and added nodes from + * mutation records into separate arrays + * while removing duplicates between both types of changes. * - * @param {MutationRecord} mutation + * @param {Array} mutations - An array of mutation records. + * @returns {Object} Object with 'removed' and 'added' nodes arrays. */ - function handleMutation(mutation) { - var addedNodes = mutation.addedNodes, - removedNodes = mutation.removedNodes; + function formChangesLists(mutations) { + var removed = [], + added = []; - if (addedNodes.length) { - formNodesList(addedNodes).forEach(processAdded); - } + mutations.forEach(function (record) { + removed = removed.concat(_.toArray(record.removedNodes)); + added = added.concat(_.toArray(record.addedNodes)); + }); - if (removedNodes.length) { - formNodesList(removedNodes).forEach(processRemoved); - } + removed = removed.filter(function (node) { + var addIndex = added.indexOf(node), + wasAdded = !!~addIndex; + + if (wasAdded) { + added.splice(addIndex, 1); + } + + return !wasAdded; + }); + + return { + removed: formNodesList(removed), + added: formNodesList(added) + }; } globalObserver = new MutationObserver(function (mutations) { - mutations.forEach(handleMutation); + var changes = formChangesLists(mutations); + + changes.removed.forEach(processRemoved); + changes.added.forEach(processAdded); }); globalObserver.observe(document.body, { @@ -240,6 +250,7 @@ define([ }); return { + /** * Adds listener for the appearance of nodes that matches provided * selector and which are inside of the provided context. Callback will be @@ -250,15 +261,19 @@ define([ * @param {HTMLElement} [ctx=document.body] - Context inside of which to search for the node. */ get: function (selector, callback, ctx) { - var data; + var data, + nodes; data = { ctx: ctx || document.body, + type: 'add', callback: callback, - type: 'add' + invoked: [] }; - getNodes(selector, data.ctx).forEach(function (node) { + nodes = $(selector, data.ctx).toArray(); + + nodes.forEach(function (node) { trigger(node, data); }); @@ -273,21 +288,29 @@ define([ * @param {HTMLElement} [ctx=document.body] - Context inside of which to search for the node. */ remove: function (selector, callback, ctx) { - var data; + var nodes = [], + data; data = { ctx: ctx || document.body, + type: 'remove', callback: callback, - type: 'remove' + invoked: [] }; - getNodes(selector, data.ctx).forEach(function (node) { - addRemovalListener(node, data); - }); + if (typeof selector === 'object') { + nodes = !_.isUndefined(selector.length) ? + _.toArray(selector) : + [selector]; + } else if (_.isString(selector)) { + nodes = $(selector, ctx).toArray(); - if (typeof selector === 'string') { addSelectorListener(selector, data); } + + nodes.forEach(function (node) { + addRemovalListener(node, data); + }); }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/raf.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/raf.js new file mode 100644 index 0000000000000000000000000000000000000000..1c174a87fce56717874f85004bf286cc3faae280 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/raf.js @@ -0,0 +1,19 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +define(function () { + 'use strict'; + + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame|| + window.onRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback){ + window.setTimeout(callback, 1000/60); + } +}); diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/actions.html b/app/code/Magento/Ui/view/base/web/templates/grid/actions.html index 29a93df1a9805a33d0e614f70d52bc2c26bbbe5b..16b8fab2bb2877bf3a0bf039c7b55368347ce992 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/actions.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/actions.html @@ -4,26 +4,23 @@ * See COPYING.txt for license details. */ --> -<div class="col-xs-2"> - <div - class="action-select-wrap" - data-bind="css: {'_active': opened}, - click: toggleOpened, - outerClick: close"> - <button - class="action-select" - data-bind="i18n: 'Select Items'"> - <span data-bind="i18n: 'Actions'"></span> - </button> - <ul - class="action-menu" - data-bind="css: {'_active': opened}, - foreach: actions"> - <li data-bind="click: $parent.applyAction.bind($parent, type)"> - <span - class="action-menu-item" - data-bind="text: label"></span> - </li> - </ul> - </div> +<div + class="action-select-wrap" + data-bind="collapsible: {onTarget: true}" + data-role="toolbar-menu-item"> + <button + class="action-select" + data-bind="i18n: 'Select Items'"> + <span data-bind="i18n: 'Actions'"></span> + </button> + <ul + class="action-menu" + data-bind="css: {'_active': $collapsible.opened}, + foreach: actions"> + <li data-bind="click: $parent.applyAction.bind($parent, type)"> + <span + class="action-menu-item" + data-bind="text: label"></span> + </li> + </ul> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/cells/actions.html b/app/code/Magento/Ui/view/base/web/templates/grid/cells/actions.html index 2a4a46a38907496a37203eb4e6f83508c461eda4..22105aed42f6d0eb382162432c3317431a32cbeb 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/cells/actions.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/cells/actions.html @@ -24,22 +24,17 @@ <!-- ko if: isMultiple(row._rowIndex) --> <div class="action-select-wrap" - data-bind=" - css : { - '_active' : opened() === row._rowIndex - }, - outerClick: closeList.bind($data, row._rowIndex)"> - <button class="action-select" data-bind="click: toggleList.bind($data, row._rowIndex)"> + data-bind="collapsible"> + <button class="action-select" data-bind="toggleCollapsible"> <span data-bind="i18n: 'Select'"></span> </button> <ul class="action-menu" - data-bind=" - css: { - '_active': opened() === row._rowIndex - }, - foreach: getVisibleActions(row._rowIndex)" - > + data-bind="css: { + '_active': $collapsible.opened + }, + foreach: getVisibleActions(row._rowIndex)" + > <li> <a class="action-menu-item" diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/cells/multiselect.html b/app/code/Magento/Ui/view/base/web/templates/grid/cells/multiselect.html index 4028c2210a5a16c7edb922cd0e4302984367774a..242c651f006d16896d55dcb52d0d8332bab9eb71 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/cells/multiselect.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/cells/multiselect.html @@ -5,13 +5,14 @@ */ --> -<td class="data-grid-checkbox-cell"> +<td class="data-grid-checkbox-cell" data-bind="visible: visible"> <label class="data-grid-checkbox-cell-inner"> <input class="admin__control-checkbox" type="checkbox" data-action="select-row" data-bind=" + disable: disabled.indexOf(row[indexField]) != -1, checked: selected, value: row[indexField], attr: { diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html b/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html index e30329410f28bc15a3fe9daf47d36c73751193df..4606d8e27b9a19e5236f6b7f28d66076805b43d1 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html @@ -5,11 +5,11 @@ */ --> -<th class="data-grid-multicheck-cell"> +<th class="data-grid-multicheck-cell" data-bind="visible: visible"> <div class="action-multicheck-wrap" - data-bind="css: { '_active': menuVisible, '_disabled': !totalRecords()}, - outerClick: hideMenu"> + data-bind="css: {'_disabled': !totalRecords()}, + collapsible"> <input id="mass-select-checkbox" class="admin__control-checkbox" @@ -22,14 +22,14 @@ <button class="action-multicheck-toggle" data-toggle="dropdown" - data-bind="css: { '_active': menuVisible }, - click: toggleMenu, - enable: totalRecords"> + data-bind="css: { '_active': $collapsible.opened }, + enable: totalRecords, + toggleCollapsible"> <span data-bind="i18n: 'Options'"></span> </button> <ul class="action-menu" - data-bind="click: hideMenu, foreach: actions"> + data-bind="closeCollapsible, foreach: actions"> <li data-bind="click: $parent[value].bind($parent), visible: $parent.isActionRelevant(value)"> <span class="action-menu-item" data-bind="text: label"></span> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html b/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html index 07b20b76bb1039bd2b6b33e0e579e09c284971f8..850f3c2fce793487392414a8ff1f95bc45f5daa5 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html @@ -7,11 +7,12 @@ <div class="admin__action-dropdown-wrap admin__data-grid-action-bookmarks" - data-bind="css: {_active: opened, _disabled: !collapsible}, outerClick: close"> + data-bind="collapsible" + data-role="toolbar-menu-item"> <button class="admin__action-dropdown" type="button" - data-bind="click: toggleOpened" + data-bind="toggleCollapsible" data-toggle="dropdown" aria-haspopup="true"> <span class="admin__action-dropdown-text" diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/view.html b/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/view.html index 7327208e7c520e4f694ad53f236a9f295848443a..707fad8eabbe87f3fc4120fd49e9b2afb6ce4b26 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/view.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/view.html @@ -5,7 +5,7 @@ */ --> <li - data-bind="css: {_edit: editing}, outerClick: endEdit"> + data-bind="css: {_edit: editing}, outerClick: endEdit.bind($data)"> <!-- ko if: editable --> <div class="action-dropdown-menu-item-edit"> <input @@ -17,18 +17,18 @@ placeholder: label }, keyboard: { - 13: function(){ $parent.close().saveView($data) } + 13: function(){ $collapsible.close(); $parent.saveView($data) } }" type="text"> <button class="action-submit" - data-bind="click: function(){ $parent.close().saveView($data) }, attr: {title: $t('Save all changes')}" + data-bind="click: $parent.saveView.bind($parent, $data), closeCollapsible, attr: {title: $t('Save all changes')}" type="button"> <span data-bind="i18n: 'Submit'"></span> </button> <div class="action-dropdown-menu-item-actions"> <button - data-bind="click: function(){ $parent.close().removeView($data) }, attr: {title: $t('Delete bookmark')}" + data-bind="click: $parent.removeView.bind($parent, $data), closeCollapsible, attr: {title: $t('Delete bookmark')}" class="action-delete" type="button"> <span data-bind="i18n: 'Delete'"></span> @@ -40,7 +40,7 @@ <a class="action-dropdown-menu-link" href="" - data-bind="text: label, click: function(){ $data.active(true); $parent.close(); }"></a> + data-bind="text: label, click: function(){ active(true); }, closeCollapsible"></a> <!-- ko if: editable --> <div class="action-dropdown-menu-item-actions"> <button @@ -52,4 +52,4 @@ </div> <!-- /ko --> </div> -</li> \ No newline at end of file +</li> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/controls/columns.html b/app/code/Magento/Ui/view/base/web/templates/grid/controls/columns.html index 41b13406d504e9b0e90d4248bd789bbe8bf81f75..e2dcc3d816376756169ad5896c5e14de65475fe8 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/controls/columns.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/controls/columns.html @@ -4,11 +4,13 @@ * See COPYING.txt for license details. */ --> -<div data-bind="css: {_active: opened}, outerClick: close" class="admin__action-dropdown-wrap admin__data-grid-action-columns"> +<div data-bind="collapsible" + class="admin__action-dropdown-wrap admin__data-grid-action-columns" + data-role="toolbar-menu-item"> <button class="admin__action-dropdown" type="button" - data-bind="click: toggleOpened" + data-bind="toggleCollapsible" data-toggle="dropdown" aria-haspopup="true"> <span class="admin__action-dropdown-text" data-bind="i18n: 'Columns'"></span> @@ -19,9 +21,22 @@ </div> <div class="admin__action-dropdown-menu-content" data-bind="foreach: elems"> <div class="admin__field-option"> - <input data-bind="attr: {id: 'grid-controls-columns-' + index}, disable: $parent.isDisabled($data), checked: visible" - class="admin__control-checkbox" type="checkbox"/> - <label data-bind="text: label, attr: {for: 'grid-controls-columns-' + index}" class="admin__field-label"></label> + <input + class="admin__control-checkbox" + type="checkbox" + data-bind=" + attr: { + id: ++ko.bindingHandlers['uniqueName'].currentIndex + '_uid' + }, + disable: $parent.isDisabled($data), + checked: visible"/> + <label + class="admin__field-label" + data-bind=" + text: label, + attr: { + for: ko.bindingHandlers['uniqueName'].currentIndex + '_uid' + }"></label> </div> </div> <div class="admin__action-dropdown-menu-footer"> @@ -31,10 +46,10 @@ </button> </div> <div class="admin__action-dropdown-footer-main-actions"> - <button data-bind="click: function () { $data.close().cancel(); }" class="action-tertiary" type="button"> + <button data-bind="click: cancel, closeCollapsible" class="action-tertiary" type="button"> <span data-bind="i18n: 'Cancel'"></span> </button> </div> </div> </div> -</div> \ No newline at end of file +</div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html b/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html index 5bdfb3db958eaf2d8c1fece8bfa2f10077fbd1f9..fe3d785f3d29a4cece894b3df02ece7467f7a6b1 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html @@ -8,7 +8,8 @@ class="data-grid-bulk-edit-panel data-grid-editable-row" data-bind=" visible: active, - foreach: fields"> + foreach: fields" + data-role="data-grid-bulk-row"> <td data-bind=" visible: $parent.getColumn(index).visible, diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/exportButton.html b/app/code/Magento/Ui/view/base/web/templates/grid/exportButton.html index d50c4fb2b9c1a169cc28fe1543154a08c468f237..84df5efc7227f9e2fde80a844b39c8017d177dea 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/exportButton.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/exportButton.html @@ -7,11 +7,11 @@ <div class="admin__action-dropdown-wrap admin__data-grid-action-export" - data-bind="css: {_active: opened, _disabled: !collapsible}, outerClick: close"> + data-bind="collapsible"> <button class="admin__action-dropdown" type="button" - data-bind="click: toggleOpened" + data-bind="toggleCollapsible" data-toggle="dropdown" aria-haspopup="true"> <span class="admin__action-dropdown-text" data-bind="i18n: 'Export'"></span> @@ -41,7 +41,7 @@ <div class="admin__action-dropdown-footer-main-actions"> <button class="action-tertiary" - data-bind="click: close" + data-bind="closeCollapsible" type="button"> <span data-bind="i18n: 'Cancel'"></span> </button> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html index 78f14ebb39fe7981649700185965a5edb4bb4e74..d0e1787de6a1dc0609f6abc91419ca2f80f21946 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html @@ -20,7 +20,7 @@ focusout: onFocusOut, keydown: keydownSwitcher }, - outerClick: outerClick"> + outerClick: outerClick.bind($data)"> <!-- ko ifnot: chipsEnabled --> <div class="action-select admin__action-multiselect" diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/filters.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/filters.html index e81c1fd1dbf968392c306554cdf4298a37a87e70..c3ae31d52b181ec49fa5ca785cb018cb4824275a 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/filters.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/filters.html @@ -4,12 +4,21 @@ * See COPYING.txt for license details. */ --> -<div class="data-grid-filters-actions-wrap"> +<div class="data-grid-filters-actions-wrap" data-bind="collapsible: {openClass: false, closeOnOuter: false}"> <div class="data-grid-filters-action-wrap"> <button - class="action-default _active" + class="action-default" data-action="grid-filter-expand" - data-bind="click: toggleOpened, attr: { disabled: !hasVisible() }, css: { _active: isOpened() }"> + data-bind=" + click: $collapsible.toggle, + attr: { + disabled: !hasVisible(), + 'title': $t('Filters') + }, + css: { + _active: hasVisible() && $collapsible.opened() + }" + > <span data-bind="i18n: 'Filters'"></span> </button> </div> @@ -21,7 +30,7 @@ <div class="admin__data-grid-filters-wrap" - data-bind="css: { _show: isOpened() }" + data-bind="css: { _show: hasVisible() && $collapsible.opened() }" data-part="filter-form"> <fieldset class="admin__fieldset admin__data-grid-filters"> @@ -49,17 +58,16 @@ class="action-tertiary" type="button" data-action="grid-filter-cancel" - data-bind="click: function(){ $data.close().cancel(); }"> + data-bind="click: $data.cancel.bind($data), closeCollapsible"> <span data-bind="i18n: 'Cancel'"></span> </button> <button class="action-secondary" type="button" data-action="grid-filter-apply" - data-bind="click: function(){ $data.close().apply(); }"> + data-bind="click: $data.apply.bind($data), closeCollapsible"> <span data-bind="i18n: 'Apply Filters'"></span> </button> </div> </div> - </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/listing.html b/app/code/Magento/Ui/view/base/web/templates/grid/listing.html index 49f0e2e392e2587762faefefb4a48e72407a3854..1a1428162c153627ca8d67ac3dad8199101a33c7 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/listing.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/listing.html @@ -4,8 +4,8 @@ * See COPYING.txt for license details. */ --> -<div class="admin__data-grid-wrap"> - <table class="data-grid"> +<div class="admin__data-grid-wrap" data-role="grid-wrapper"> + <table class="data-grid" data-role="grid"> <thead> <tr data-bind="foreach: elems"> <!-- ko template: getHeader() --><!-- /ko --> @@ -14,6 +14,7 @@ <tbody> <!-- ko foreach: { data: rows, as: 'row' } --> <tr class="data-row" + data-role="row" data-bind=" css: { '_odd-row': !!($index() % 2) @@ -35,4 +36,4 @@ <!-- /ko --> </tbody> </table> -</div> \ No newline at end of file +</div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/paging-total.html b/app/code/Magento/Ui/view/base/web/templates/grid/paging-total.html new file mode 100644 index 0000000000000000000000000000000000000000..0c79131bc2f8e4c994858b5e5d157641d571c5fd --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/paging-total.html @@ -0,0 +1,13 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div class="admin__control-support-text"> + <span data-bind="i18n: totalRecords"></span> records found + <!-- ko if: totalSelected --> + (<span data-bind="i18n: totalSelected"></span> selected) + <!-- /ko --> +</div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/paging/paging.html b/app/code/Magento/Ui/view/base/web/templates/grid/paging/paging.html index 354193c7901b2596244f364fdc8af9a659478864..1961cfff11a3d7be640aace14992455fbb0981f8 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/paging/paging.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/paging/paging.html @@ -4,16 +4,7 @@ * See COPYING.txt for license details. */ --> -<div class="col-xs-3"> - <div class="admin__control-support-text"> - <span data-bind="text: totalRecords"></span> records found - <!-- ko if: totalSelected --> - (<span data-bind="text: totalSelected"></span> selected) - <!-- /ko --> - </div> -</div> - -<div class="col-xs-7 admin__data-grid-pager-wrap"> +<div class="admin__data-grid-pager-wrap" data-role="toolbar-menu-item"> <!-- ko scope: sizes --> <!-- ko template: getTemplate() --><!-- /ko --> <!-- /ko --> @@ -41,5 +32,4 @@ <span>Next page</span> </button> </div> - </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/paging/sizes.html b/app/code/Magento/Ui/view/base/web/templates/grid/paging/sizes.html index ff61b6bdcf6f57d728cbf2225a262868ea8e675c..dcc9692793e40d2508b061fdf1a33a5f98c1bc6c 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/paging/sizes.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/paging/sizes.html @@ -7,9 +7,10 @@ <div class="selectmenu" - data-bind="css: {_active: opened}, outerClick: close"> + data-bind="collapsible" + data-role="toolbar-collapsible-menu"> <div - data-bind="click: open" + data-bind="openCollapsible" class="selectmenu-value"> <input data-bind="value: _value, attr: {id: index}" @@ -18,11 +19,13 @@ <button class="selectmenu-toggle" type="button" - data-bind="click: toggleOpened, css: {_active: opened}" + data-bind="toggleCollapsible, css: {_active: $collapsible.opened}" aria-haspopup="true"> <span data-bind="i18n: 'Select'"></span> </button> - <div class="selectmenu-items" data-bind="css: {_active: opened}, outerClick: $data.discardAll.bind($data)"> + <div class="selectmenu-items" data-bind="css: {_active: $collapsible.opened}, + outerClick: discardAll.bind($data)" + data-role="toolbar-collapsible-menu"> <ul> <!-- ko foreach: optionsArray --> <li data-bind="css: { _edit: $parent.isEditing(value)}"> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html b/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html index 1a9d495f422965f61401709e6736a7a61c4662fc..3e518b7f874f2881ede0dd2edfa318b232c061cd 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/search/search.html @@ -5,7 +5,11 @@ */ --> <div class="data-grid-search-control-wrap"> + <label class="data-grid-search-label" for="data-grid-search" data-bind="attr: {title: $t('Search')}"> + <span data-bind="text: $t('Search')"></span> + </label> <input + id="data-grid-search" class="admin__control-text data-grid-search-control" data-bind=" attr: { @@ -20,4 +24,4 @@ <button class="action-submit" data-bind="click: apply.bind($data, false)" type="button"> <span data-bind="i18n: 'Search'"></span> </button> -</div> \ No newline at end of file +</div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/chips.html b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/chips.html new file mode 100644 index 0000000000000000000000000000000000000000..e396c223d8a92a5486335ffb6367c95f087322e6 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/chips.html @@ -0,0 +1,57 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div + class="admin__data-grid-filters-current" + data-bind="css: {_show: hasData()}"> + <div class="admin__current-filters-title-wrap"> + <span + class="admin__current-filters-title" + data-bind="i18n: 'Active filters:'"></span> + </div> + <div class="admin__current-filters-list-wrap"> + <ul class="admin__current-filters-list" data-role="filter-list"> + <!-- ko foreach: elems --> + <!-- ko foreach: previews --> + <li> + <span data-bind="text: label + ':'"></span> + + <!-- ko if: typeof preview === 'string' --> + <span data-bind="text: preview"></span> + <!-- /ko --> + + <!-- ko if: typeof preview === 'object' --> + <span> + <!-- ko text: preview[0] || '...' --><!-- /ko --> - <!-- ko text: preview[1] || '...' --><!-- /ko --> + </span> + <!-- /ko --> + + <button + class="action-remove" + data-action="grid-filter-remove-chip" + data-bind="click: $parent.clear.bind($parent, elem)" + type="button"> + <span data-bind="i18n: 'Remove'"></span> + </button> + </li> + <!-- /ko --> + <!-- /ko --> + </ul> + </div> + <div class="admin__current-filters-actions-wrap"> + <button + class="action-tertiary action-clear" + type="button" + data-action="grid-filter-reset" + data-bind=" + i18n: 'Clear all', + click: clear, + attr: { + 'data-action': hasData() ? 'grid-filter-reset' : '' + }" + ></button> + </div> +</div> \ No newline at end of file diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/filters.html b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/filters.html new file mode 100644 index 0000000000000000000000000000000000000000..9a7e06d5699a531809b9527972df744197a6feab --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/filters.html @@ -0,0 +1,22 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="data-grid-filters-actions-wrap"> + <div class="data-grid-filters-action-wrap"> + <button + class="action-default" + data-action="grid-filter-expand" + data-bind=" + click: function(){ + window.scrollTo(0, 0); + $data.trigger('open'); + }, + attr: {disabled: !hasVisible()}"> + <span data-bind="i18n: 'Filters'"></span> + </button> + <span class="filters-active" data-bind="text: $data.active().length || ''"></span> <!-- Added the amount of selected filters --> + </div> +</div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/listing.html b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/listing.html new file mode 100644 index 0000000000000000000000000000000000000000..fc0c43a4b43315304418d1e1ecbdc602236c06c9 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/listing.html @@ -0,0 +1,17 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="admin__data-grid-wrap" data-role="sticky-el-root"> + <table class="data-grid"> + <thead> + <tr data-bind="foreach: elems"> + <!-- ko template: getHeader() --><!-- /ko --> + </tr> + </thead> + <tbody> + </tbody> + </table> +</div> \ No newline at end of file diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html new file mode 100644 index 0000000000000000000000000000000000000000..ec79fd08472ea7a588e5611796f8e6a090b7e43e --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html @@ -0,0 +1,22 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div data-bind='visible: visible' class='sticky-header'> + <span class="data-grid-cap-left"></span> + <span class="data-grid-cap-right"></span> + <!-- ko if: enableToolbar --> + <!-- ko scope: toolbar --> + <!-- ko template: $data.stickyTmpl || getTemplate() --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + + <!-- ko if: enableHeader --> + <!-- ko scope: listing --> + <!-- ko template: $data.stickyTmpl || getTemplate() --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> +</div> \ No newline at end of file diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/toolbar.html b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/toolbar.html new file mode 100644 index 0000000000000000000000000000000000000000..156873520509f2e1c3cc25168f761102344d448d --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/toolbar.html @@ -0,0 +1,41 @@ +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div class="admin__data-grid-header" data-role="sticky-el-root"> + <div class="admin__data-grid-header-row"> + <!-- ko foreach: getRegion('bottom') --> + <!-- ko if: $data.componentType === 'massaction' --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + <!-- ko foreach: getRegion('bottom') --> + <!-- ko if: $data.componentType === 'paging' --> + <!-- ko template: totalTmpl --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + <!-- ko foreach: getRegion('dataGridFilters') --> + <!-- ko template: $data.stickyTmpl || getTemplate() --><!-- /ko --> + <!-- /ko --> + <div class="admin__data-grid-actions-wrap"> + <!-- ko foreach: getRegion('dataGridActions') --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!-- /ko --> + </div> + <!-- ko foreach: getRegion('bottom') --> + <!-- ko if: $data.componentType === 'paging' --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + </div> +</div> +<!-- ko foreach: getRegion('dataGridFilters') --> + <!-- ko if: $data.index === 'listing_filters' --> + <!-- ko scope: chips --> + <!-- ko template: $data.stickyTmpl || getTemplate() --> + <!-- /ko --> + <!-- /ko --> +<!-- /ko --> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/toolbar.html b/app/code/Magento/Ui/view/base/web/templates/grid/toolbar.html index 0812e3fe862d2fac42f48fdd80ead31355f23107..36482b1d973bae48f5d946dff86c27f6c0bc724e 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/toolbar.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/toolbar.html @@ -5,20 +5,43 @@ */ --> -<div class="admin__data-grid-header"> +<div class="admin__data-grid-header" + data-role="data-grid-toolbar"> <div class="admin__data-grid-header-row"> <div class="admin__data-grid-actions-wrap"> <!-- ko foreach: getRegion('dataGridActions') --> - <!-- ko template: getTemplate() --><!-- /ko --> + <!-- ko template: getTemplate() --><!-- /ko --> <!-- /ko --> </div> <!-- ko foreach: getRegion('dataGridFilters') --> - <!-- ko template: getTemplate() --><!-- /ko --> + <!-- ko template: getTemplate() --><!-- /ko --> <!-- /ko --> </div> <div class="admin__data-grid-header-row row row-gutter"> - <!-- ko foreach: getRegion('bottom') --> - <!-- ko template: getTemplate() --><!-- /ko --> - <!-- /ko --> + <div class="col-xs-2"> + <!-- ko foreach: getRegion('bottom') --> + <!-- ko if: $data.componentType === 'massaction' --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + </div> + <div class="col-xs-10"> + <div class="row"> + <div class="col-xs-3"> + <!-- ko foreach: getRegion('bottom') --> + <!-- ko if: $data.componentType === 'paging' --> + <!-- ko template: totalTmpl --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + </div> + <div class="col-xs-9"> + <!-- ko foreach: getRegion('bottom') --> + <!-- ko if: $data.componentType === 'paging' --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> + </div> + </div> + </div> </div> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/tree-massactions.html b/app/code/Magento/Ui/view/base/web/templates/grid/tree-massactions.html index db716f2a01323031a01ea4538bfa4689c2574df9..5a3f2997181c8499d0675de42d62acfd16c176b1 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/tree-massactions.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/tree-massactions.html @@ -4,35 +4,33 @@ * See COPYING.txt for license details. */ --> -<div class="col-xs-2" data-bind="outerClick: close"> - <div - class="action-select-wrap" - data-bind="css: {'_active': opened}"> - <button - class="action-select" - data-bind="title: $t('Select Items'), click: toggleOpened"> - <span data-bind="text: $t('Actions')"></span> - </button> - <div class="action-menu-items"> - <ul - class="action-menu" - data-bind="css: {'_active': opened}, - foreach: {data: actions, as: 'action'}"> - <li +<div + class="action-select-wrap" + data-bind="css: {'_active': opened}, outerClick: close.bind($data)"> + <button + class="action-select" + data-bind="title: $t('Select Items'), click: toggleOpened"> + <span data-bind="text: $t('Actions')"></span> + </button> + <div class="action-menu-items"> + <ul + class="action-menu" + data-bind="css: {'_active': opened}, + foreach: {data: actions, as: 'action'}"> + <li data-bind="css: { '_visible': $data.visible, '_parent': $data.actions}"> - <span - class="action-menu-item" - data-bind="text: label, - click: $parent.applyAction.bind($parent, type)"> - </span> - <!-- ko if: $data.actions --> - <!-- ko template: {name: $parent.submenuTemplate, data: $parent} --> - <!-- /ko --> - <!-- /ko--> - </li> - </ul> - </div> + <span + class="action-menu-item" + data-bind="text: label, + click: $parent.applyAction.bind($parent, type)"> + </span> + <!-- ko if: $data.actions --> + <!-- ko template: {name: $parent.submenuTemplate, data: $parent} --> + <!-- /ko --> + <!-- /ko--> + </li> + </ul> </div> </div> diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html index c946719cc3dba6619d32c16ca020b0b2542d209b..e8ec20c9454d6d0034122ef05ee6bce62b645cd1 100644 --- a/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html +++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/select.html @@ -13,9 +13,14 @@ placeholder: placeholder }, hasFocus: focused, - optgroup: options, + options: options, value: value, optionsCaption: caption, optionsValue: 'value', - optionsText: 'label'" + optionsText: 'label', + optionsAfterRender: function(option, item) { + if (item && item.disabled) { + ko.applyBindingsToNode(option, {attr: {disabled: true}}, item); + } + }" /> diff --git a/app/code/Magento/Ui/view/frontend/web/templates/group/group.html b/app/code/Magento/Ui/view/frontend/web/templates/group/group.html index 762cc4595c55e524adebd351f4162298aa2ed13a..c31399dded3981b2e3e1ca6fb83716c19667d7b8 100644 --- a/app/code/Magento/Ui/view/frontend/web/templates/group/group.html +++ b/app/code/Magento/Ui/view/frontend/web/templates/group/group.html @@ -4,10 +4,10 @@ * See COPYING.txt for license details. */ --> -<div class="field" data-bind="css: additionalClasses"> - <label class="label"> +<fieldset class="field" data-bind="css: additionalClasses"> + <legend class="label"> <span data-bind="text: element.label"></span> - </label> + </legend> <div class="control"> <!-- ko foreach: { data: elems, as: 'element' } --> @@ -33,4 +33,4 @@ <!-- /ko --> <!-- /ko --> </div> -</div> +</fieldset> diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index 4850b8e004952102abe0f66c31e66102f723a0aa..5d5f159d3d233bdd826f3b73d84fe4fe6c4f56f7 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -186,28 +186,6 @@ class Carrier extends AbstractCarrierOnline implements CarrierInterface ); } - /** - * Get RateRequest object - * @return RateRequest - */ - public function getRequest() - { - return $this->_request; - } - - /** - * Extends the behavior of the parent function - * @return bool - */ - public function canCollectRates() - { - $request = $this->getRequest(); - - return - parent::canCollectRates() - && (int)$request->getData('package_weight') < (int)$this->getConfigData('min_package_weight'); - } - /** * Collect and get rates/errors * diff --git a/app/code/Magento/User/Model/Backend/Config/ObserverConfig.php b/app/code/Magento/User/Model/Backend/Config/ObserverConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..adcb28325dc531d11ae5177bf0bed5f1474e3d83 --- /dev/null +++ b/app/code/Magento/User/Model/Backend/Config/ObserverConfig.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Model\Backend\Config; + +/** + * User backend observer helper class + */ +class ObserverConfig +{ + /** + * Backend configuration interface + * + * @var \Magento\Backend\App\ConfigInterface + */ + protected $backendConfig; + + /** + * @param \Magento\Backend\App\ConfigInterface $backendConfig + */ + public function __construct( + \Magento\Backend\App\ConfigInterface $backendConfig + ) { + $this->backendConfig = $backendConfig; + } + + /** + * Check if latest password is expired + * + * @param array $latestPassword + * @return bool + */ + public function _isLatestPasswordExpired($latestPassword) + { + if (!isset($latestPassword['expires']) || $this->getAdminPasswordLifetime() == 0) { + return false; + } else { + return (int)$latestPassword['expires'] < time(); + } + } + + /** + * Get admin lock threshold from configuration + * @return int + */ + public function getAdminLockThreshold() + { + return 60 * (int)$this->backendConfig->getValue('admin/security/lockout_threshold'); + } + + /** + * Check whether password change is forced + * + * @return bool + */ + public function isPasswordChangeForced() + { + return (bool)(int)$this->backendConfig->getValue('admin/security/password_is_forced'); + } + + /** + * Get admin password lifetime + * + * @return int + */ + public function getAdminPasswordLifetime() + { + return 86400 * (int)$this->backendConfig->getValue('admin/security/password_lifetime'); + } + + /** + * Get admin maxiumum security failures from config + * + * @return int + */ + public function getMaxFailures() + { + return (int)$this->backendConfig->getValue('admin/security/lockout_failures'); + } +} diff --git a/app/code/Magento/User/Model/Backend/Observer/AuthObserver.php b/app/code/Magento/User/Model/Backend/Observer/AuthObserver.php new file mode 100644 index 0000000000000000000000000000000000000000..f682202a22320a8cc80a2c673f9d3bf8be867c1c --- /dev/null +++ b/app/code/Magento/User/Model/Backend/Observer/AuthObserver.php @@ -0,0 +1,198 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Model\Backend\Observer; + +use Magento\Framework\Event\Observer as EventObserver; +use Magento\Framework\Exception\State\UserLockedException; +use Magento\Framework\Encryption\Encryptor; +use Magento\User\Model\User; + +/** + * User backend observer model for authentication + */ +class AuthObserver +{ + /** + * Backend configuration interface + * + * @var \Magento\User\Model\Backend\Config\ObserverConfig + */ + protected $observerConfig; + + /** + * Admin user resource model + * + * @var \Magento\User\Model\Resource\User + */ + protected $userResource; + + /** + * Backend url interface + * + * @var \Magento\Backend\Model\UrlInterface + */ + protected $url; + + /** + * Backend authorization session + * + * @var \Magento\Backend\Model\Auth\Session + */ + protected $authSession; + + /** + * Factory class for user model + * + * @var \Magento\User\Model\UserFactory + */ + protected $userFactory; + + /** + * Encryption model + * + * @var \Magento\Framework\Encryption\EncryptorInterface + */ + protected $encryptor; + + /** + * Message manager interface + * + * @var \Magento\Framework\Message\ManagerInterface + */ + protected $messageManager; + + /** + * @param \Magento\User\Model\Backend\Config\ObserverConfig $observerConfig + * @param \Magento\User\Model\Resource\User $userResource + * @param \Magento\Backend\Model\UrlInterface $url + * @param \Magento\Backend\Model\Auth\Session $authSession + * @param \Magento\User\Model\UserFactory $userFactory + * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor + * @param \Magento\Framework\Message\ManagerInterface $messageManager + */ + public function __construct( + \Magento\User\Model\Backend\Config\ObserverConfig $observerConfig, + \Magento\User\Model\Resource\User $userResource, + \Magento\Backend\Model\UrlInterface $url, + \Magento\Backend\Model\Auth\Session $authSession, + \Magento\User\Model\UserFactory $userFactory, + \Magento\Framework\Encryption\EncryptorInterface $encryptor, + \Magento\Framework\Message\ManagerInterface $messageManager + ) { + $this->observerConfig = $observerConfig; + $this->userResource = $userResource; + $this->url = $url; + $this->authSession = $authSession; + $this->userFactory = $userFactory; + $this->encryptor = $encryptor; + $this->messageManager = $messageManager; + } + + /** + * Admin locking and password hashing upgrade logic implementation + * + * @param EventObserver $observer + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function adminAuthenticate($observer) + { + $password = $observer->getEvent()->getPassword(); + /** @var User $user */ + $user = $observer->getEvent()->getUser(); + $authResult = $observer->getEvent()->getResult(); + + if (!$authResult && $user->getId()) { + // update locking information regardless whether user locked or not + $this->_updateLockingInformation($user); + } + + // check whether user is locked + $lockExpires = $user->getLockExpires(); + if ($lockExpires) { + $lockExpires = new \DateTime($lockExpires); + if ($lockExpires > new \DateTime()) { + throw new UserLockedException( + __('You did not sign in correctly or your account is temporarily disabled.') + ); + } + } + + if (!$authResult) { + return; + } + + $this->userResource->unlock($user->getId()); + + $latestPassword = $this->userResource->getLatestPassword($user->getId()); + $this->_checkExpiredPassword($latestPassword); + + if (!$this->encryptor->validateHashVersion($user->getPassword(), true)) { + $user->setPassword($password) + ->setData('force_new_password', true) + ->save(); + } + } + + /** + * Update locking information for the user + * + * @param \Magento\User\Model\User $user + * @return void + */ + private function _updateLockingInformation($user) + { + $now = new \DateTime(); + $lockThreshold = $this->observerConfig->getAdminLockThreshold(); + $maxFailures = $this->observerConfig->getMaxFailures(); + if (!($lockThreshold && $maxFailures)) { + return; + } + $failuresNum = (int)$user->getFailuresNum() + 1; + /** @noinspection PhpAssignmentInConditionInspection */ + if ($firstFailureDate = $user->getFirstFailure()) { + $firstFailureDate = new \DateTime($firstFailureDate); + } + + $newFirstFailureDate = false; + $updateLockExpires = false; + $lockThreshInterval = new \DateInterval('PT' . $lockThreshold.'S'); + // set first failure date when this is first failure or last first failure expired + if (1 === $failuresNum || !$firstFailureDate || $now->diff($firstFailureDate) > $lockThreshInterval) { + $newFirstFailureDate = $now; + // otherwise lock user + } elseif ($failuresNum >= $maxFailures) { + $updateLockExpires = $now->add($lockThreshInterval); + } + $this->userResource->updateFailure($user, $updateLockExpires, $newFirstFailureDate); + } + + /** + * Check whether the latest password is expired + * Side-effect can be when passwords were changed with different lifetime configuration settings + * + * @param array $latestPassword + * @return void + */ + private function _checkExpiredPassword($latestPassword) + { + if ($latestPassword && $this->observerConfig->_isLatestPasswordExpired($latestPassword)) { + if ($this->observerConfig->isPasswordChangeForced()) { + $message = __('It\'s time to change your password.'); + } else { + $myAccountUrl = $this->url->getUrl('adminhtml/system_account/'); + $message = __('It\'s time to <a href="%1">change your password</a>.', $myAccountUrl); + } + $this->messageManager->addNoticeMessage($message); + $message = $this->messageManager->getMessages()->getLastAddedMessage(); + if ($message) { + $message->setIdentifier('magento_user_password_expired')->setIsSticky(true); + $this->authSession->setPciAdminUserIsPasswordExpired(true); + } + } + } +} diff --git a/app/code/Magento/User/Model/Backend/Observer.php b/app/code/Magento/User/Model/Backend/Observer/PasswordObserver.php similarity index 51% rename from app/code/Magento/User/Model/Backend/Observer.php rename to app/code/Magento/User/Model/Backend/Observer/PasswordObserver.php index 07fb1a45fd42c7edb6d512eb53a56d87ce208804..93c54fd5df9a7461770265c6cfbf562e55739e94 100644 --- a/app/code/Magento/User/Model/Backend/Observer.php +++ b/app/code/Magento/User/Model/Backend/Observer/PasswordObserver.php @@ -3,33 +3,29 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\User\Model\Backend; + +namespace Magento\User\Model\Backend\Observer; use Magento\Framework\Event\Observer as EventObserver; -use Magento\Framework\Exception\State\UserLockedException; -use Magento\Framework\Encryption\Encryptor; /** - * User backend observer model - * - * Implements hashes upgrading - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * User backend observer model for passwords */ -class Observer +class PasswordObserver { /** - * Authorization interface + * Backend configuration interface * - * @var \Magento\Framework\AuthorizationInterface + * @var \Magento\User\Model\Backend\Config\ObserverConfig */ - protected $authorization; + protected $observerConfig; /** - * Backend configuration interface + * Authorization interface * - * @var \Magento\Backend\App\ConfigInterface + * @var \Magento\Framework\AuthorizationInterface */ - protected $backendConfig; + protected $authorization; /** * Admin user resource model @@ -59,13 +55,6 @@ class Observer */ protected $authSession; - /** - * Factory class for user model - * - * @var \Magento\User\Model\UserFactory - */ - protected $userFactory; - /** * Encryption model * @@ -89,166 +78,37 @@ class Observer /** * @param \Magento\Framework\AuthorizationInterface $authorization - * @param \Magento\Backend\App\ConfigInterface $backendConfig + * @param \Magento\User\Model\Backend\Config\ObserverConfig $observerConfig * @param \Magento\User\Model\Resource\User $userResource * @param \Magento\Backend\Model\UrlInterface $url * @param \Magento\Backend\Model\Session $session * @param \Magento\Backend\Model\Auth\Session $authSession - * @param \Magento\User\Model\UserFactory $userFactory * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor * @param \Magento\Framework\App\ActionFlag $actionFlag * @param \Magento\Framework\Message\ManagerInterface $messageManager - * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\AuthorizationInterface $authorization, - \Magento\Backend\App\ConfigInterface $backendConfig, + \Magento\User\Model\Backend\Config\ObserverConfig $observerConfig, \Magento\User\Model\Resource\User $userResource, \Magento\Backend\Model\UrlInterface $url, \Magento\Backend\Model\Session $session, \Magento\Backend\Model\Auth\Session $authSession, - \Magento\User\Model\UserFactory $userFactory, \Magento\Framework\Encryption\EncryptorInterface $encryptor, \Magento\Framework\App\ActionFlag $actionFlag, \Magento\Framework\Message\ManagerInterface $messageManager ) { $this->authorization = $authorization; - $this->backendConfig = $backendConfig; + $this->observerConfig = $observerConfig; $this->userResource = $userResource; $this->url = $url; $this->session = $session; $this->authSession = $authSession; - $this->userFactory = $userFactory; $this->encryptor = $encryptor; $this->actionFlag = $actionFlag; $this->messageManager = $messageManager; } - /** - * Admin locking and password hashing upgrade logic implementation - * - * @param EventObserver $observer - * @return void - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function adminAuthenticate($observer) - { - $password = $observer->getEvent()->getPassword(); - $user = $observer->getEvent()->getUser(); - $authResult = $observer->getEvent()->getResult(); - - if (!$authResult && $user->getId()) { - // update locking information regardless whether user locked or not - $this->_updateLockingInformation($user); - } - - // check whether user is locked - $lockExpires = $user->getLockExpires(); - if ($lockExpires) { - $lockExpires = new \DateTime($lockExpires); - if ($lockExpires > new \DateTime()) { - throw new UserLockedException( - __('You did not sign in correctly or your account is temporarily disabled.') - ); - } - } - - if (!$authResult) { - return; - } - - $this->userResource->unlock($user->getId()); - - $latestPassword = $this->userResource->getLatestPassword($user->getId()); - $this->_checkExpiredPassword($latestPassword); - - // upgrade admin password - $isValidHash = $this->encryptor->isValidHashByVersion( - $password, - $user->getPassword(), - Encryptor::HASH_VERSION_LATEST - ); - if (!$isValidHash) { - $this->userFactory->create() - ->load($user->getId()) - ->setNewPassword($password) - ->setForceNewPassword(true) - ->save(); - } - } - - /** - * Update locking information for the user - * - * @param \Magento\User\Model\User $user - * @return void - */ - private function _updateLockingInformation($user) - { - $now = new \DateTime(); - $lockThreshold = $this->getAdminLockThreshold(); - $maxFailures = (int)$this->backendConfig->getValue('admin/security/lockout_failures'); - if (!($lockThreshold && $maxFailures)) { - return; - } - $failuresNum = (int)$user->getFailuresNum() + 1; - if ($firstFailureDate = $user->getFirstFailure()) { - $firstFailureDate = new \DateTime($firstFailureDate); - } - - $updateFirstFailureDate = false; - $updateLockExpires = false; - $lockThresholdInterval = new \DateInterval('PT' . $lockThreshold.'S'); - // set first failure date when this is first failure or last first failure expired - if (1 === $failuresNum || !$firstFailureDate || $now->diff($firstFailureDate) > $lockThresholdInterval) { - $updateFirstFailureDate = $now; - // otherwise lock user - } elseif ($failuresNum >= $maxFailures) { - $updateLockExpires = $now->add($lockThresholdInterval); - } - $this->userResource->updateFailure($user, $updateLockExpires, $updateFirstFailureDate); - } - - /** - * Check whether the latest password is expired - * Side-effect can be when passwords were changed with different lifetime configuration settings - * - * @param array $latestPassword - * @return void - */ - private function _checkExpiredPassword($latestPassword) - { - if ($latestPassword && $this->_isLatestPasswordExpired($latestPassword)) { - if ($this->isPasswordChangeForced()) { - $message = __('It\'s time to change your password.'); - } else { - $myAccountUrl = $this->url->getUrl('adminhtml/system_account/'); - $message = __('It\'s time to <a href="%1">change your password</a>.', $myAccountUrl); - } - $this->messageManager->addNotice($message); - $message = $this->messageManager->getMessages()->getLastAddedMessage(); - if ($message) { - $message->setIdentifier('magento_user_password_expired')->setIsSticky(true); - $this->authSession->setPciAdminUserIsPasswordExpired(true); - } - } - } - - /** - * Check if latest password is expired - * - * @param array $latestPassword - * @return bool - */ - protected function _isLatestPasswordExpired($latestPassword) - { - if (!isset($latestPassword['expires']) || $this->getAdminPasswordLifetime() == 0) { - return false; - } else { - return (int)$latestPassword['expires'] < time(); - } - } - /** * Harden admin password change. * @@ -271,7 +131,7 @@ class Observer } if ($password && !$user->getForceNewPassword() && $user->getId()) { - if ($this->encryptor->validateHash($password, $user->getOrigData('password'))) { + if ($this->encryptor->isValidHash($password, $user->getOrigData('password'))) { throw new \Magento\Framework\Exception\LocalizedException( __('Sorry, but this password has already been used. Please create another.') ); @@ -302,7 +162,7 @@ class Observer $user = $observer->getEvent()->getObject(); if ($user->getId()) { $password = $user->getNewPassword(); - $passwordLifetime = $this->getAdminPasswordLifetime(); + $passwordLifetime = $this->observerConfig->getAdminPasswordLifetime(); if ($passwordLifetime && $password && !$user->getForceNewPassword()) { $resource = $this->userResource; $passwordHash = $this->encryptor->getHash($password, false); @@ -313,25 +173,6 @@ class Observer } } - /** - * Get admin lock threshold from configuration - * @return int - */ - public function getAdminLockThreshold() - { - return 60 * (int)$this->backendConfig->getValue('admin/security/lockout_threshold'); - } - - /** - * Get admin password lifetime - * - * @return int - */ - public function getAdminPasswordLifetime() - { - return 86400 * (int)$this->backendConfig->getValue('admin/security/password_lifetime'); - } - /** * Force admin to change password * @@ -340,11 +181,10 @@ class Observer */ public function forceAdminPasswordChange($observer) { - if (!$this->isPasswordChangeForced()) { + if (!$this->observerConfig->isPasswordChangeForced()) { return; } - $session = $this->authSession; - if (!$session->isLoggedIn()) { + if (!$this->authSession->isLoggedIn()) { return; } $actionList = [ @@ -352,9 +192,11 @@ class Observer 'adminhtml_system_account_save', 'adminhtml_auth_logout', ]; + /** @var \Magento\Framework\App\Action\Action $controller */ $controller = $observer->getEvent()->getControllerAction(); /** @var \Magento\Framework\App\RequestInterface $request */ $request = $observer->getEvent()->getRequest(); + if ($this->authSession->getPciAdminUserIsPasswordExpired()) { if (!in_array($request->getFullActionName(), $actionList)) { if ($this->authorization->isAllowed('Magento_Backend::myaccount')) { @@ -368,7 +210,7 @@ class Observer */ $this->authSession->clearStorage(); $this->session->clearStorage(); - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('Your password has expired; please contact your administrator.') ); $controller->getRequest()->setDispatched(false); @@ -376,14 +218,4 @@ class Observer } } } - - /** - * Check whether password change is forced - * - * @return bool - */ - public function isPasswordChangeForced() - { - return (bool)(int)$this->backendConfig->getValue('admin/security/password_is_forced'); - } } diff --git a/app/code/Magento/User/Model/Resource/User.php b/app/code/Magento/User/Model/Resource/User.php index 9f2bf7f3d36599da3d9a48a0e6cd7291f89ef5dd..dc2f9c55f35ec5bcec961f881d6ed42c1129424e 100644 --- a/app/code/Magento/User/Model/Resource/User.php +++ b/app/code/Magento/User/Model/Resource/User.php @@ -522,8 +522,8 @@ class User extends \Magento\Framework\Model\Resource\Db\AbstractDb * Increment failures count along with updating lock expire and first failure dates * * @param ModelUser $user - * @param int|false $setLockExpires - * @param int|false $setFirstFailure + * @param int|bool $setLockExpires + * @param int|bool $setFirstFailure * @return void */ public function updateFailure($user, $setLockExpires = false, $setFirstFailure = false) diff --git a/app/code/Magento/User/Setup/UpgradeData.php b/app/code/Magento/User/Setup/UpgradeData.php new file mode 100644 index 0000000000000000000000000000000000000000..37b291a5a2c610037ed08b50013b5cedd1dfa18e --- /dev/null +++ b/app/code/Magento/User/Setup/UpgradeData.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\User\Setup; + +use Magento\Framework\Encryption\Encryptor; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\UpgradeDataInterface; + +class UpgradeData implements UpgradeDataInterface +{ + + /** + * @inheritdoc + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $this->upgradeHash($setup); + } + + $setup->endSetup(); + } + + /** + * @param ModuleDataSetupInterface $setup + * @return void + */ + private function upgradeHash($setup) + { + $customerEntityTable = $setup->getTable('admin_user'); + + $select = $setup->getConnection()->select()->from( + $customerEntityTable, + ['user_id', 'password'] + ); + + $customers = $setup->getConnection()->fetchAll($select); + foreach ($customers as $customer) { + list($hash, $salt) = explode(Encryptor::DELIMITER, $customer['password']); + + $newHash = $customer['password']; + if (strlen($hash) === 32) { + $newHash = implode(Encryptor::DELIMITER, [$hash, $salt, Encryptor::HASH_VERSION_MD5]); + } elseif (strlen($hash) === 64) { + $newHash = implode(Encryptor::DELIMITER, [$hash, $salt, Encryptor::HASH_VERSION_SHA256]); + } + + $bind = ['password' => $newHash]; + $where = ['user_id = ?' => (int)$customer['user_id']]; + $setup->getConnection()->update($customerEntityTable, $bind, $where); + } + } +} diff --git a/app/code/Magento/User/Test/Unit/Model/Backend/Observer/AuthObserverTest.php b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/AuthObserverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..97be558ef833b228d674bb4ac05525406398a376 --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/AuthObserverTest.php @@ -0,0 +1,267 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Test\Unit\Model\Backend\Observer; + +use Magento\Framework\Exception\State\UserLockedException; + +/** + * Test class for Magento\User\Model\Backend\Observer\AuthObserver + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AuthObserverTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\User\Model\Backend\Config\ObserverConfig */ + protected $observerConfig; + + /** @var \Magento\Backend\App\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $configInterfaceMock; + + /** @var \Magento\User\Model\Resource\User|\PHPUnit_Framework_MockObject_MockObject */ + protected $userMock; + + /** @var \Magento\Backend\Model\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $urlInterfaceMock; + + /** @var \Magento\Backend\Model\Auth\Session|\PHPUnit_Framework_MockObject_MockObject */ + protected $authSessionMock; + + /** @var \Magento\User\Model\UserFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $userFactoryMock; + + /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $encryptorMock; + + /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $managerInterfaceMock; + + /** @var \Magento\Framework\Message\MessageInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $messageInterfaceMock; + + /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $eventManagerMock; + + /** @var \Magento\User\Model\Backend\Observer\AuthObserver */ + protected $model; + + public function setUp() + { + $this->configInterfaceMock = $this->getMockBuilder('Magento\Backend\App\ConfigInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->userMock = $this->getMockBuilder('Magento\User\Model\Resource\User') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->urlInterfaceMock = $this->getMockBuilder('Magento\Backend\Model\UrlInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->authSessionMock = $this->getMockBuilder('Magento\Backend\Model\Auth\Session') + ->disableOriginalConstructor() + ->setMethods( + [ + 'setPciAdminUserIsPasswordExpired', + 'unsPciAdminUserIsPasswordExpired', + 'getPciAdminUserIsPasswordExpired', + 'isLoggedIn', + 'clearStorage' + ] + )->getMock(); + + $this->userFactoryMock = $this->getMockBuilder('Magento\User\Model\UserFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\EncryptorInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->managerInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\ManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->messageInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\MessageInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->eventManagerMock = $this->getMockBuilder('Magento\Framework\Event\ManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMockForAbstractClass(); + + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->observerConfig = $helper->getObject( + 'Magento\User\Model\Backend\Config\ObserverConfig', + [ + 'backendConfig' => $this->configInterfaceMock + ] + ); + + $this->model = $helper->getObject( + 'Magento\User\Model\Backend\Observer\AuthObserver', + [ + 'observerConfig' => $this->observerConfig, + 'userResource' => $this->userMock, + 'url' => $this->urlInterfaceMock, + 'authSession' => $this->authSessionMock, + 'userFactory' => $this->userFactoryMock, + 'encryptor' => $this->encryptorMock, + 'messageManager' => $this->managerInterfaceMock, + 'messageInterface' => $this->messageInterfaceMock, + 'eventManager' => $this->eventManagerMock + ] + ); + } + + public function testAdminAuthenticate() + { + $password = "myP@sw0rd"; + $uid = 123; + $authResult = true; + $lockExpires = false; + $userPassword = [ + 'expires' => 1, + ]; + + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getPassword', 'getUser', 'getResult']) + ->getMock(); + + /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ + $userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getLockExpires', 'getPassword', 'save']) + ->getMock(); + + $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getPassword')->willReturn($password); + $eventMock->expects($this->once())->method('getUser')->willReturn($userMock); + $eventMock->expects($this->once())->method('getResult')->willReturn($authResult); + $userMock->expects($this->atLeastOnce())->method('getId')->willReturn($uid); + $userMock->expects($this->once())->method('getLockExpires')->willReturn($lockExpires); + $this->userMock->expects($this->once())->method('unlock'); + $this->userMock->expects($this->once())->method('getLatestPassword')->willReturn($userPassword); + $this->configInterfaceMock + ->expects($this->atLeastOnce()) + ->method('getValue') + ->willReturn(1); + + /** @var \Magento\Framework\Message\Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder('Magento\Framework\Message\Collection') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->managerInterfaceMock->expects($this->once())->method('getMessages')->willReturn($collectionMock); + $collectionMock + ->expects($this->once()) + ->method('getLastAddedMessage') + ->willReturn($this->messageInterfaceMock); + $this->messageInterfaceMock->expects($this->once())->method('setIdentifier')->willReturnSelf(); + $this->authSessionMock->expects($this->once())->method('setPciAdminUserIsPasswordExpired'); + $this->encryptorMock->expects($this->once())->method('validateHashVersion')->willReturn(false); + + $this->model->adminAuthenticate($eventObserverMock); + } + + public function testAdminAuthenticateThrowsException() + { + $password = "myP@sw0rd"; + $authResult = true; + $lockExpires = '3015-07-08 11:14:15.638276'; + + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getPassword', 'getUser', 'getResult']) + ->getMock(); + + /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ + $userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getLockExpires', 'getPassword']) + ->getMock(); + + $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getPassword')->willReturn($password); + $eventMock->expects($this->once())->method('getUser')->willReturn($userMock); + $eventMock->expects($this->once())->method('getResult')->willReturn($authResult); + $userMock->expects($this->once())->method('getLockExpires')->willReturn($lockExpires); + + try { + $this->model->adminAuthenticate($eventObserverMock); + } catch (UserLockedException $expected) { + return; + } + $this->fail('An expected exception has not been raised.'); + } + + public function testAdminAuthenticateUpdateLockingInfo() + { + $password = "myP@sw0rd"; + $uid = 123; + $authResult = false; + $firstFailure = '1965-07-08 11:14:15.638276'; + $numOfFailures = 5; + + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getPassword', 'getUser', 'getResult']) + ->getMock(); + + /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ + $userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getFailuresNum', 'getFirstFailure']) + ->getMock(); + + $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getPassword')->willReturn($password); + $eventMock->expects($this->once())->method('getUser')->willReturn($userMock); + $eventMock->expects($this->once())->method('getResult')->willReturn($authResult); + $userMock->expects($this->once())->method('getId')->willReturn($uid); + $this->configInterfaceMock + ->expects($this->atLeastOnce()) + ->method('getValue') + ->willReturn(1); + $userMock->expects($this->once())->method('getFailuresNum')->willReturn($numOfFailures); + $userMock->expects($this->once())->method('getFirstFailure')->willReturn($firstFailure); + $this->userMock->expects($this->once())->method('updateFailure'); + + $this->model->adminAuthenticate($eventObserverMock); + } +} diff --git a/app/code/Magento/User/Test/Unit/Model/Backend/Observer/PasswordObserverTest.php b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/PasswordObserverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..331d74ac086d392fac09a4a78a4b3bd30f56ca23 --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Model/Backend/Observer/PasswordObserverTest.php @@ -0,0 +1,308 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Test\Unit\Model\Backend\Observer; + +/** + * Test class for Magento\User\Model\Backend\Observer\PasswordObserver + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class PasswordObserverTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\Framework\AuthorizationInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $authMock; + + /** @var \Magento\User\Model\Backend\Config\ObserverConfig */ + protected $observerConfig; + + /** @var \Magento\Backend\App\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $configInterfaceMock; + + /** @var \Magento\User\Model\Resource\User|\PHPUnit_Framework_MockObject_MockObject */ + protected $userMock; + + /** @var \Magento\Backend\Model\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $urlInterfaceMock; + + /** @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject */ + protected $sessionMock; + + /** @var \Magento\Backend\Model\Auth\Session|\PHPUnit_Framework_MockObject_MockObject */ + protected $authSessionMock; + + /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $encryptorMock; + + /** @var \Magento\Framework\App\ActionFlag|\PHPUnit_Framework_MockObject_MockObject */ + protected $actionFlagMock; + + /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $managerInterfaceMock; + + /** @var \Magento\Framework\Message\MessageInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $messageInterfaceMock; + + /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $eventManagerMock; + + /** @var \Magento\User\Model\Backend\Observer\PasswordObserver */ + protected $model; + + public function setUp() + { + $this->authMock = $this->getMockBuilder('Magento\Framework\AuthorizationInterface') + ->disableOriginalConstructor() + ->setMethods(['isAllowed']) + ->getMock(); + + $this->configInterfaceMock = $this->getMockBuilder('Magento\Backend\App\ConfigInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->userMock = $this->getMockBuilder('Magento\User\Model\Resource\User') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->urlInterfaceMock = $this->getMockBuilder('Magento\Backend\Model\UrlInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->sessionMock = $this->getMockBuilder('Magento\Backend\Model\Session') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->authSessionMock = $this->getMockBuilder('Magento\Backend\Model\Auth\Session') + ->disableOriginalConstructor() + ->setMethods( + [ + 'setPciAdminUserIsPasswordExpired', + 'unsPciAdminUserIsPasswordExpired', + 'getPciAdminUserIsPasswordExpired', + 'isLoggedIn', + 'clearStorage' + ] + )->getMock(); + + $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\EncryptorInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->actionFlagMock = $this->getMockBuilder('Magento\Framework\App\ActionFlag') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->managerInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\ManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->messageInterfaceMock = $this->getMockBuilder('Magento\Framework\Message\MessageInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->eventManagerMock = $this->getMockBuilder('Magento\Framework\Event\ManagerInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMockForAbstractClass(); + + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->observerConfig = $helper->getObject( + 'Magento\User\Model\Backend\Config\ObserverConfig', + [ + 'backendConfig' => $this->configInterfaceMock + ] + ); + + $this->model = $helper->getObject( + 'Magento\User\Model\Backend\Observer\PasswordObserver', + [ + 'observerConfig' => $this->observerConfig, + 'authorization' => $this->authMock, + 'userResource' => $this->userMock, + 'url' => $this->urlInterfaceMock, + 'session' => $this->sessionMock, + 'authSession' => $this->authSessionMock, + 'actionFlag' => $this->actionFlagMock, + 'encryptor' => $this->encryptorMock, + 'messageManager' => $this->managerInterfaceMock, + 'messageInterface' => $this->messageInterfaceMock, + 'eventManager' => $this->eventManagerMock + ] + ); + } + + public function testCheckAdminPasswordChange() + { + $newPW = "mYn3wpassw0rd"; + $uid = 123; + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getObject']) + ->getMock(); + + /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ + $userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword']) + ->getMock(); + + $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getObject')->willReturn($userMock); + $userMock->expects($this->atLeastOnce())->method('getNewPassword')->willReturn($newPW); + $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false); + $userMock->expects($this->once())->method('getId')->willReturn($uid); + $this->encryptorMock->expects($this->once())->method('isValidHash')->willReturn(false); + $this->encryptorMock->expects($this->once())->method('getHash')->willReturn(md5($newPW)); + $this->userMock->method('getOldPasswords')->willReturn([md5('pw1'), md5('pw2')]); + + $this->model->checkAdminPasswordChange($eventObserverMock); + } + + public function testCheckAdminPasswordChangeThrowsLocalizedExp() + { + $newPW = "mYn3wpassw0rd"; + $uid = 123; + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getObject']) + ->getMock(); + + /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ + $userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword']) + ->getMock(); + + $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getObject')->willReturn($userMock); + $userMock->expects($this->atLeastOnce())->method('getNewPassword')->willReturn($newPW); + $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false); + $userMock->expects($this->once())->method('getId')->willReturn($uid); + $this->encryptorMock->expects($this->once())->method('isValidHash')->willReturn(true); + $this->userMock->method('getOldPasswords')->willReturn([md5('pw1'), md5('pw2')]); + + try { + $this->model->checkAdminPasswordChange($eventObserverMock); + } catch (\Magento\Framework\Exception\LocalizedException $expected) { + return; + } + $this->fail('An expected exception has not been raised.'); + } + + public function testTrackAdminPassword() + { + $newPW = "mYn3wpassw0rd"; + $oldPW = "notsecure"; + $uid = 123; + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getObject']) + ->getMock(); + + /** @var \Magento\User\Model\User|\PHPUnit_Framework_MockObject_MockObject $userMock */ + $userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['getId', 'getNewPassword', 'getForceNewPassword']) + ->getMock(); + + $eventObserverMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getObject')->willReturn($userMock); + $userMock->expects($this->once())->method('getId')->willReturn($uid); + $userMock->expects($this->once())->method('getNewPassword')->willReturn($newPW); + $this->configInterfaceMock + ->expects($this->atLeastOnce()) + ->method('getValue') + ->willReturn(1); + $userMock->expects($this->once())->method('getForceNewPassword')->willReturn(false); + $this->encryptorMock->expects($this->once())->method('getHash')->willReturn(md5($oldPW)); + + /** @var \Magento\Framework\Message\Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder('Magento\Framework\Message\Collection') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->managerInterfaceMock + ->expects($this->once()) + ->method('getMessages') + ->willReturn($collectionMock); + $this->authSessionMock->expects($this->once())->method('unsPciAdminUserIsPasswordExpired')->willReturn(null); + + $this->model->trackAdminNewPassword($eventObserverMock); + } + + public function testForceAdminPasswordChange() + { + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $eventObserverMock */ + $eventObserverMock = $this->getMockBuilder('Magento\Framework\Event\Observer') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + /** @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject */ + $eventMock = $this->getMockBuilder('Magento\Framework\Event') + ->disableOriginalConstructor() + ->setMethods(['getControllerAction', 'getRequest']) + ->getMock(); + + $this->configInterfaceMock + ->expects($this->atLeastOnce()) + ->method('getValue') + ->willReturn(1); + $this->authSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(true); + $eventObserverMock->expects($this->atLeastOnce())->method('getEvent')->willReturn($eventMock); + /** @var \Magento\Framework\App\Action\Action $controllerMock */ + $controllerMock = $this->getMockBuilder('Magento\Framework\App\Action\AbstractAction') + ->disableOriginalConstructor() + ->setMethods(['getRedirect', 'getRequest']) + ->getMockForAbstractClass(); + /** @var \Magento\Framework\App\RequestInterface $requestMock */ + $requestMock = $this->getMockBuilder('Magento\Framework\App\RequestInterface') + ->disableOriginalConstructor() + ->setMethods(['getFullActionName', 'setDispatched']) + ->getMockForAbstractClass(); + $eventMock->expects($this->once())->method('getControllerAction')->willReturn($controllerMock); + $eventMock->expects($this->once())->method('getRequest')->willReturn($requestMock); + $this->authSessionMock->expects($this->once())->method('getPciAdminUserIsPasswordExpired')->willReturn(true); + $requestMock->expects($this->once())->method('getFullActionName')->willReturn('not_in_array'); + + $this->authSessionMock->expects($this->once())->method('clearStorage'); + $this->sessionMock->expects($this->once())->method('clearStorage'); + $this->managerInterfaceMock->expects($this->once())->method('addErrorMessage'); + $controllerMock->expects($this->once())->method('getRequest')->willReturn($requestMock); + $requestMock->expects($this->once())->method('setDispatched')->willReturn(false); + + $this->model->forceAdminPasswordChange($eventObserverMock); + } +} diff --git a/app/code/Magento/User/Test/Unit/Model/Resource/UserTest.php b/app/code/Magento/User/Test/Unit/Model/Resource/UserTest.php new file mode 100755 index 0000000000000000000000000000000000000000..961cc40d5f3304599b2610c780d2d2774424af3b --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Model/Resource/UserTest.php @@ -0,0 +1,442 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Test\Unit\Model\Resource; + +/** + * Test class for \Magento\User\Model\Resource\User testing + */ +class UserTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\User\Model\Resource\User */ + protected $model; + + /** @var \Magento\User\Model\User|\PHPUnit_framework_MockObject_MockObject */ + protected $userMock; + + /** @var \Magento\Framework\Acl\CacheInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $aclCacheMock; + + /** @var \Magento\Framework\Model\Resource\Db\Context|\PHPUnit_Framework_MockObject_MockObject */ + protected $contextMock; + + /** @var \Magento\Authorization\Model\RoleFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $roleFactoryMock; + + /** @var \Magento\Framework\Stdlib\DateTime|\PHPUnit_Framework_MockObject_MockObject */ + protected $dateTimeMock; + + /** @var \Magento\Framework\App\Resource|\PHPUnit_Framework_MockObject_MockObject */ + protected $resourceMock; + + /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $dbAdapterMock; + + /** @var \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject */ + protected $selectMock; + + /** @var \Magento\Authorization\Model\Role|\PHPUnit_Framework_MockObject_MockObject */ + protected $roleMock; + + protected function setUp() + { + $this->userMock = $this->getMockBuilder('Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->resourceMock = $this->getMockBuilder('Magento\Framework\App\Resource') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->aclCacheMock = $this->getMockBuilder('Magento\Framework\Acl\CacheInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->roleFactoryMock = $this->getMockBuilder('Magento\Authorization\Model\RoleFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->roleMock = $this->getMockBuilder('Magento\Authorization\Model\Role') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + + $this->dateTimeMock = $this->getMockBuilder('Magento\Framework\Stdlib\DateTime') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->selectMock = $this->getMockBuilder('Magento\Framework\DB\Select') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->dbAdapterMock = $this->getMockBuilder('Magento\Framework\DB\Adapter\AdapterInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $helper->getObject( + 'Magento\User\Model\Resource\User', + [ + 'resource' => $this->resourceMock, + 'aclCache' => $this->aclCacheMock, + 'roleFactory' => $this->roleFactoryMock, + 'dateTime' => $this->dateTimeMock + ] + ); + } + + public function testIsUserUnique() + { + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchRow')->willReturn([true]); + + $this->assertFalse($this->model->isUserUnique($this->userMock)); + } + + public function testRecordLogin() + { + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update'); + + $this->assertInstanceOf('Magento\User\Model\Resource\User', $this->model->recordLogin($this->userMock)); + } + + public function testLoadByUsername() + { + $returnData = [1, 2, 3]; + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchRow')->willReturn($returnData); + + $this->assertEquals($returnData, $this->model->loadByUsername('user1')); + } + + + public function testHasAssigned2Role() + { + $returnData = [1, 2, 3]; + $uid = 1234; + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->selectMock->expects($this->once())->method('from')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchAll')->willReturn($returnData); + + $this->assertEquals($returnData, $this->model->hasAssigned2Role($uid)); + $this->assertNull($this->model->hasAssigned2Role(0)); + } + + public function testHasAssigned2RolePassAnObject() + { + $methodUserMock = $this->getMockBuilder('\Magento\Framework\Model\AbstractModel') + ->disableOriginalConstructor() + ->setMethods(['getUserId']) + ->getMock(); + $returnData = [1, 2, 3]; + $uid = 1234; + $methodUserMock->expects($this->once())->method('getUserId')->willReturn($uid); + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchAll')->willReturn($returnData); + + $this->assertEquals($returnData, $this->model->hasAssigned2Role($methodUserMock)); + } + + public function testClearUserRoles() + { + $uid = 123; + $this->userMock->expects($this->once())->method('getId')->willReturn($uid); + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('delete'); + $this->model->_clearUserRoles($this->userMock); + } + + public function testDeleteSucess() + { + $uid = 123; + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('beginTransaction'); + $this->userMock->expects($this->once())->method('getId')->willReturn($uid); + $this->dbAdapterMock->expects($this->atLeastOnce())->method('delete'); + + $this->assertTrue($this->model->delete($this->userMock)); + } + + public function testGetRolesEmptyId() + { + $this->assertEquals([], $this->model->getRoles($this->userMock)); + } + + public function testGetRolesReturnFilledArray() + { + $uid = 123; + $this->userMock->expects($this->atLeastOnce())->method('getId')->willReturn($uid); + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('joinLeft')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchCol')->willReturn([1, 2, 3]); + $this->assertEquals([1, 2, 3], $this->model->getRoles($this->userMock)); + } + + public function testGetRolesFetchRowFailure() + { + $uid = 123; + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->userMock->expects($this->atLeastOnce())->method('getId')->willReturn($uid); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('joinLeft')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchCol')->willReturn(false); + $this->assertEquals([], $this->model->getRoles($this->userMock)); + } + + public function testSaveExtraEmptyId() + { + $this->resourceMock->expects($this->never())->method('getConnection'); + $this->assertInstanceOf( + 'Magento\User\Model\Resource\User', + $this->model->saveExtra($this->userMock, [1, 2, 3]) + ); + } + + public function testSaveExtraFilledId() + { + $uid = 123; + $this->userMock->expects($this->atLeastOnce())->method('getId')->willReturn($uid); + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update'); + $this->assertInstanceOf( + 'Magento\User\Model\Resource\User', + $this->model->saveExtra($this->userMock, [1, 2, 3]) + ); + } + + public function testCountAll() + { + $returnData = 123; + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchOne')->willReturn($returnData); + $this->assertEquals($returnData, $this->model->countAll()); + } + + public function testUpdateRoleUsersAclWithUsers() + { + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->roleMock->expects($this->once())->method('getRoleUsers')->willReturn(['user1', 'user2']); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn(1); + $this->assertTrue($this->model->updateRoleUsersAcl($this->roleMock)); + } + + public function testUpdateRoleUsersAclNoUsers() + { + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->roleMock->expects($this->once())->method('getRoleUsers')->willReturn([]); + $this->dbAdapterMock->expects($this->never())->method('update'); + $this->assertFalse($this->model->updateRoleUsersAcl($this->roleMock)); + } + + public function testUpdateRoleUsersAclUpdateFail() + { + $this->resourceMock->expects($this->once())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->roleMock->expects($this->once())->method('getRoleUsers')->willReturn(['user1', 'user2']); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn(0); + $this->assertFalse($this->model->updateRoleUsersAcl($this->roleMock)); + } + + public function testUnlock() + { + $inputData = [1, 2, 3]; + $returnData = 5; + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn($returnData); + $this->assertEquals($returnData, $this->model->unlock($inputData)); + } + + public function testUnlockWithInteger() + { + $inputData = 123; + $returnData = 5; + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn($returnData); + $this->assertEquals($returnData, $this->model->unlock($inputData)); + } + + public function testLock() + { + $inputData = [1, 2, 3]; + $returnData = 5; + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn($returnData); + $this->assertEquals($returnData, $this->model->lock($inputData, 1, 1)); + } + + public function testLockWithInteger() + { + $inputData = 123; + $returnData = 5; + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn($returnData); + $this->assertEquals($returnData, $this->model->lock($inputData, 1, 1)); + } + + public function testGetOldPassword() + { + $returnData = ['password1', 'password2']; + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('order')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->atLeastOnce())->method('fetchCol')->willReturn($returnData); + $this->assertEquals($returnData, $this->model->getOldPasswords($this->userMock)); + } + + public function testDeleteFromRole() + { + $methodUserMock = $this->getMockBuilder('\Magento\Framework\Model\AbstractModel') + ->disableOriginalConstructor() + ->setMethods(['getUserId', 'getRoleId']) + ->getMock(); + $uid = 1234; + $roleId = 44; + $methodUserMock->expects($this->once())->method('getUserId')->willReturn($uid); + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $methodUserMock->expects($this->atleastOnce())->method('getRoleId')->willReturn($roleId); + $this->dbAdapterMock->expects($this->once())->method('delete'); + + $this->assertInstanceOf('\Magento\User\Model\Resource\User', $this->model->deleteFromRole($methodUserMock)); + } + + public function testRoleUserExists() + { + $methodUserMock = $this->getMockBuilder('\Magento\Framework\Model\AbstractModel') + ->disableOriginalConstructor() + ->setMethods(['getUserId', 'getRoleId']) + ->getMock(); + $uid = 1234; + $roleId = 44; + $returnData = [1, 2, 3]; + $methodUserMock->expects($this->atLeastOnce())->method('getUserId')->willReturn($uid); + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $methodUserMock->expects($this->once())->method('getRoleId')->willReturn($roleId); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('fetchCol')->willReturn($returnData); + + $this->assertEquals($returnData, $this->model->roleUserExists($methodUserMock)); + $this->assertEquals([], $this->model->roleUserExists($this->userMock)); + } + + public function testGetValidationBeforeSave() + { + $this->assertInstanceOf('\Zend_Validate_Callback', $this->model->getValidationRulesBeforeSave()); + } + + public function testUpdateFailure() + { + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('update')->willReturn($this->selectMock); + $this->dbAdapterMock->expects($this->once())->method('quoteInto')->willReturn($this->selectMock); + $this->model->updateFailure($this->userMock, 1, 1); + } + + public function testTrackPassword() + { + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('insert')->willReturn($this->selectMock); + $this->model->trackPassword($this->userMock, "myPas#w0rd", 1); + } + + public function testGetLatestPassword() + { + $uid = 123; + $returnData = ['password1', 'password2']; + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->once())->method('fetchRow')->willReturn($returnData); + $this->dbAdapterMock->expects($this->once())->method('select')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('from')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('where')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('order')->willReturn($this->selectMock); + $this->selectMock->expects($this->atLeastOnce())->method('limit')->willReturn($this->selectMock); + $this->assertEquals($returnData, $this->model->getLatestPassword($uid)); + } + + public function testInitUniqueFields() + { + $this->assertInstanceOf( + '\Magento\User\Model\Resource\User', + $this->invokeMethod($this->model, '_initUniqueFields', []) + ); + } + + public function testBeforeSave() + { + $this->userMock->expects($this->once())->method('isObjectNew')->willReturn(true); + + $this->assertInstanceOf( + '\Magento\User\Model\Resource\User', + $this->invokeMethod($this->model, '_beforeSave', [$this->userMock]) + ); + } + + public function testAfterSave() + { + $roleId = 123; + $methodUserMock = $this->getMockBuilder('\Magento\User\Model\User') + ->disableOriginalConstructor() + ->setMethods(['hasRoleId', 'getRoleId']) + ->getMock(); + $methodUserMock->expects($this->once())->method('hasRoleId')->willReturn(true); + $methodUserMock->expects($this->once())->method('getRoleId')->willReturn($roleId); + $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); + $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($this->roleMock); + $this->roleMock->expects($this->once())->method('load')->willReturn($this->roleMock); + $this->roleMock->expects($this->atLeastOnce())->method('getId')->willReturn($roleId); + $this->dbAdapterMock->expects($this->once())->method('describeTable')->willReturn([1, 2, 3]); + + $this->assertInstanceOf( + '\Magento\User\Model\Resource\User', + $this->invokeMethod($this->model, '_afterSave', [$methodUserMock]) + ); + } + + /** + * Call protected/private method of a class. + * + * @param $object + * @param $methodName + * @param array $parameters + * @return mixed + */ + public function invokeMethod(&$object, $methodName, array $parameters = []) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + return $method->invokeArgs($object, $parameters); + } +} diff --git a/app/code/Magento/User/etc/adminhtml/di.xml b/app/code/Magento/User/etc/adminhtml/di.xml old mode 100644 new mode 100755 index ad550cbf5058d575c84d8cc51624e1b37d63310e..4278e618075fa3b873af383689163f65378c36e5 --- a/app/code/Magento/User/etc/adminhtml/di.xml +++ b/app/code/Magento/User/etc/adminhtml/di.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> <preference for="Magento\Backend\Model\Auth\Credential\StorageInterface" type="Magento\User\Model\User" /> - <type name="Magento\User\Model\Backend\Observer"> + <type name="Magento\User\Model\Backend\Observer\AuthObserver"> <arguments> <argument name="authorization" xsi:type="object">Magento\Framework\Authorization</argument> </arguments> diff --git a/app/code/Magento/User/etc/adminhtml/events.xml b/app/code/Magento/User/etc/adminhtml/events.xml old mode 100644 new mode 100755 index 01f8142a549d0219985cc2e6d3cb98fc24122df0..3bdfe2f33f7e6ae17732feec72e3be5e98158400 --- a/app/code/Magento/User/etc/adminhtml/events.xml +++ b/app/code/Magento/User/etc/adminhtml/events.xml @@ -7,15 +7,15 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/Event/etc/events.xsd"> <event name="admin_user_authenticate_after"> - <observer name="magento_user" instance="Magento\User\Model\Backend\Observer" method="adminAuthenticate" /> + <observer name="magento_user" instance="Magento\User\Model\Backend\Observer\AuthObserver" method="adminAuthenticate" /> </event> <event name="controller_action_predispatch"> - <observer name="magento_user" instance="Magento\User\Model\Backend\Observer" method="forceAdminPasswordChange" /> + <observer name="magento_user" instance="Magento\User\Model\Backend\Observer\PasswordObserver" method="forceAdminPasswordChange" /> </event> <event name="admin_user_save_before"> - <observer name="magento_user" instance="Magento\User\Model\Backend\Observer" method="checkAdminPasswordChange" /> + <observer name="magento_user" instance="Magento\User\Model\Backend\Observer\PasswordObserver" method="checkAdminPasswordChange" /> </event> <event name="admin_user_save_after"> - <observer name="magento_user" instance="Magento\User\Model\Backend\Observer" method="trackAdminNewPassword" /> + <observer name="magento_user" instance="Magento\User\Model\Backend\Observer\PasswordObserver" method="trackAdminNewPassword" /> </event> </config> diff --git a/app/code/Magento/User/etc/module.xml b/app/code/Magento/User/etc/module.xml index d0467cb70e860606c563d75a4deb29b59967ab6a..8bb2b470ccf553f51f260f94097338440af5a64b 100644 --- a/app/code/Magento/User/etc/module.xml +++ b/app/code/Magento/User/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> - <module name="Magento_User" setup_version="2.0.0"> + <module name="Magento_User" setup_version="2.0.1"> <sequence> <module name="Magento_Backend"/> </sequence> diff --git a/app/code/Magento/Webapi/Controller/PathProcessor.php b/app/code/Magento/Webapi/Controller/PathProcessor.php index be8dd0a09c86d0e3d9f475960a8e96af4d0f4f4c..6142cebf5536910133eede9a4f1b9a3feb7f48e2 100644 --- a/app/code/Magento/Webapi/Controller/PathProcessor.php +++ b/app/code/Magento/Webapi/Controller/PathProcessor.php @@ -53,7 +53,6 @@ class PathProcessor $this->storeManager->setCurrentStore($storeCode); $path = '/' . (isset($pathParts[1]) ? $pathParts[1] : ''); } else { - $this->storeManager->setCurrentStore(\Magento\Store\Model\Store::DEFAULT_CODE); $path = '/' . implode('/', $pathParts); } return $path; diff --git a/app/code/Magento/Webapi/etc/di.xml b/app/code/Magento/Webapi/etc/di.xml index 28d00c467ea83ed0f8973860c67544efaef81294..593ef6427082d1d149c84a4d761567e28a7594e4 100644 --- a/app/code/Magento/Webapi/etc/di.xml +++ b/app/code/Magento/Webapi/etc/di.xml @@ -21,18 +21,6 @@ <type name="Magento\Framework\Xml\Generator" shared="false" /> <type name="Magento\Framework\Xml\Parser" shared="false" /> <type name="Magento\Framework\Code\Scanner\DirectoryScanner" shared="false" /> - <type name="Magento\Server\Reflection" shared="false" /> - <type name="Magento\Framework\Reflection\MethodsMap"> - <arguments> - <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Webapi</argument> - </arguments> - </type> - <type name="Magento\Framework\Reflection\DataObjectProcessor"> - <arguments> - <argument name="extensionAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\ExtensionAttributesProcessor\Proxy</argument> - <argument name="customAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\CustomAttributesProcessor\Proxy</argument> - </arguments> - </type> <type name="Magento\Integration\Model\ConfigBasedIntegrationManager"> <plugin name="webapiSetup" type="Magento\Webapi\Model\Plugin\Manager" /> </type> diff --git a/app/code/Magento/Weee/Block/Item/Price/Renderer.php b/app/code/Magento/Weee/Block/Item/Price/Renderer.php index 2dbf97c41bac8c4ddb3908e30bb0eee6b214ef2c..be142e502a73c39cc2548fde58fc7145ed5dfe8d 100644 --- a/app/code/Magento/Weee/Block/Item/Price/Renderer.php +++ b/app/code/Magento/Weee/Block/Item/Price/Renderer.php @@ -61,7 +61,7 @@ class Renderer extends \Magento\Tax\Block\Item\Price\Renderer if (!$displayWeeeDetails) { return false; } - if ($this->weeeHelper->getAppliedAmount($this->getItem()) <= 0) { + if ($this->weeeHelper->getWeeeTaxAppliedAmount($this->getItem()) <= 0) { return false; } @@ -182,7 +182,7 @@ class Renderer extends \Magento\Tax\Block\Item\Price\Renderer } if ($this->getIncludeWeeeFlag()) { - return $priceExclTax + $this->getItem()->getWeeeTaxAppliedAmount(); + return $priceExclTax + $this->weeeHelper->getWeeeTaxAppliedAmount($this->getItem()); } return $priceExclTax; @@ -224,7 +224,7 @@ class Renderer extends \Magento\Tax\Block\Item\Price\Renderer } if ($this->getIncludeWeeeFlag()) { - return $rowTotalExclTax + $this->getItem()->getWeeeTaxAppliedRowAmount(); + return $rowTotalExclTax + $this->weeeHelper->getWeeeTaxAppliedRowAmount($this->getItem()); } return $rowTotalExclTax; @@ -328,7 +328,7 @@ class Renderer extends \Magento\Tax\Block\Item\Price\Renderer return $priceExclTax; } - return $priceExclTax + $this->getItem()->getWeeeTaxAppliedAmount(); + return $priceExclTax + $this->weeeHelper->getWeeeTaxAppliedAmount($this->getItem()); } /** @@ -360,7 +360,7 @@ class Renderer extends \Magento\Tax\Block\Item\Price\Renderer return $rowTotalExclTax; } - return $rowTotalExclTax + $this->getItem()->getWeeeTaxAppliedRowAmount(); + return $rowTotalExclTax + $this->weeeHelper->getWeeeTaxAppliedRowAmount($this->getItem()); } /** @@ -396,7 +396,7 @@ class Renderer extends \Magento\Tax\Block\Item\Price\Renderer return false; } - if (!$this->getItem()->getWeeeTaxAppliedAmount()) { + if ($this->weeeHelper->getWeeeTaxAppliedAmount($this->getItem()) <= 0) { return false; } return true; diff --git a/app/code/Magento/Weee/Helper/Data.php b/app/code/Magento/Weee/Helper/Data.php index 516cdcca07b889b2d2152bd88001b7e37a3451b0..55db9791896ecb8307b01bee407fb1bf1a84eaa9 100644 --- a/app/code/Magento/Weee/Helper/Data.php +++ b/app/code/Magento/Weee/Helper/Data.php @@ -296,15 +296,38 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper * Returns applied weee tax amount * * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item - * @return array + * @return float + */ + public function getWeeeTaxAppliedAmount($item) + { + return $this->getRecursiveNumericAmount($item, 'getWeeeTaxAppliedAmount'); + } + + /** + * Returns applied weee tax amount for the row + * + * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item + * @return float */ - public function getAppliedAmount($item) + public function getWeeeTaxAppliedRowAmount($item) + { + return $this->getRecursiveNumericAmount($item, 'getWeeeTaxAppliedRowAmount'); + } + + /** + * Returns accumulated amounts for the item + * + * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item + * @param string $functionName + * @return float + */ + protected function getRecursiveNumericAmount($item, $functionName) { if ($item instanceof \Magento\Quote\Model\Quote\Item\AbstractItem) { if ($item->getHasChildren() && $item->isChildrenCalculated()) { $result = 0; foreach ($item->getChildren() as $child) { - $childData = $this->getAppliedAmount($child); + $childData = $this->getRecursiveNumericAmount($child, $functionName); if (!empty($childData)) { $result += $childData; } @@ -313,8 +336,7 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper } } - // if order item data is old enough then weee_tax_applied might not be valid - $data = $item->getWeeeTaxAppliedAmount(); + $data = $item->$functionName(); if (empty($data)) { return 0; } diff --git a/app/code/Magento/Weee/Pricing/Render/Adjustment.php b/app/code/Magento/Weee/Pricing/Render/Adjustment.php index 1dea449b0396d8d9b4c2cd6eb6b9b39f72d5aea8..6b5ba5dc001a4b9827ec2dd8bcf014d889d429ba 100644 --- a/app/code/Magento/Weee/Pricing/Render/Adjustment.php +++ b/app/code/Magento/Weee/Pricing/Render/Adjustment.php @@ -12,7 +12,7 @@ use Magento\Framework\View\Element\Template; use Magento\Weee\Model\Tax; /** - * Weee Tax Price Adjustment + * Weee Price Adjustment that handles Weee specific amount and its display */ class Adjustment extends AbstractAdjustment { @@ -105,7 +105,7 @@ class Adjustment extends AbstractAdjustment */ public function showInclDescr() { - return $this->isDisplayFpt() && $this->getWeeeTaxAmount() && $this->typeOfDisplay(Tax::DISPLAY_INCL_DESCR); + return $this->isWeeeShown() && $this->getWeeeTaxAmount() && $this->typeOfDisplay(Tax::DISPLAY_INCL_DESCR); } /** @@ -115,7 +115,7 @@ class Adjustment extends AbstractAdjustment */ public function showExclDescrIncl() { - return $this->isDisplayFpt() && $this->getWeeeTaxAmount() && $this->typeOfDisplay(Tax::DISPLAY_EXCL_DESCR_INCL); + return $this->isWeeeShown() && $this->getWeeeTaxAmount() && $this->typeOfDisplay(Tax::DISPLAY_EXCL_DESCR_INCL); } /** @@ -125,7 +125,7 @@ class Adjustment extends AbstractAdjustment */ public function getWeeeTaxAttributes() { - return $this->isDisplayFpt() ? $this->getWeeeAttributesForDisplay() : []; + return $this->isWeeeShown() ? $this->getWeeeAttributesForDisplay() : []; } /** @@ -174,13 +174,13 @@ class Adjustment extends AbstractAdjustment } /** - * Define if the FPT should be displayed + * Returns whether Weee should be displayed * * @return bool */ - protected function isDisplayFpt() + protected function isWeeeShown() { - $isDisplayFpt = $this->typeOfDisplay([Tax::DISPLAY_INCL_DESCR, Tax::DISPLAY_EXCL_DESCR_INCL]); - return $isDisplayFpt; + $isWeeeShown = $this->typeOfDisplay([Tax::DISPLAY_INCL_DESCR, Tax::DISPLAY_EXCL_DESCR_INCL]); + return $isWeeeShown; } } diff --git a/app/code/Magento/Weee/Pricing/Render/TaxAdjustment.php b/app/code/Magento/Weee/Pricing/Render/TaxAdjustment.php new file mode 100644 index 0000000000000000000000000000000000000000..25948c84d9e7a823be5fb2161353912b0c4f94be --- /dev/null +++ b/app/code/Magento/Weee/Pricing/Render/TaxAdjustment.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Weee\Pricing\Render; + +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Framework\View\Element\Template; +use Magento\Weee\Model\Tax; + +/** + * Weee Price Adjustment that overrides part of the Tax module's Adjustment + */ +class TaxAdjustment extends \Magento\Tax\Pricing\Render\Adjustment +{ + /** + * Weee helper + * + * @var \Magento\Weee\Helper\Data + */ + protected $weeeHelper; + + /** + * @param Template\Context $context + * @param PriceCurrencyInterface $priceCurrency + * @param \Magento\Tax\Helper\Data $helper + * @param \Magento\Weee\Helper\Data $weeeHelper + * @param array $data + */ + public function __construct( + Template\Context $context, + PriceCurrencyInterface $priceCurrency, + \Magento\Tax\Helper\Data $helper, + \Magento\Weee\Helper\Data $weeeHelper, + array $data = [] + ) { + $this->weeeHelper = $weeeHelper; + parent::__construct($context, $priceCurrency, $helper, $data); + } + + /** + * Returns the list of default exclusions + * + * @return array + */ + public function getDefaultExclusions() + { + $exclusions = parent::getDefaultExclusions(); + + //Determine if the Weee amount should be excluded from the price + if ($this->typeOfDisplay([Tax::DISPLAY_EXCL_DESCR_INCL, Tax::DISPLAY_EXCL])) { + $exclusions[] = \Magento\Weee\Pricing\Adjustment::ADJUSTMENT_CODE; + } + + return $exclusions; + } + + /** + * Returns display type for price accordingly to current zone + * + * @param int|int[]|null $compareTo + * @param \Magento\Store\Model\Store|null $store + * @return bool|int + */ + protected function typeOfDisplay($compareTo = null, $store = null) + { + return $this->weeeHelper->typeOfDisplay($compareTo, $this->getZone(), $store); + } +} diff --git a/app/code/Magento/Weee/Test/Unit/Helper/DataTest.php b/app/code/Magento/Weee/Test/Unit/Helper/DataTest.php index 90cd4c084f94408dc4adeff20fadb75436b70e1a..38c36f0c3a99101d3431a8a4b18570a9c85e1ab2 100644 --- a/app/code/Magento/Weee/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Weee/Test/Unit/Helper/DataTest.php @@ -284,12 +284,14 @@ class DataTest extends \PHPUnit_Framework_TestCase $this->assertEquals($testArray, $this->helperData->getApplied($itemProductBundle)); } - public function testGetAppliedAmountSimple() + public function testGetRecursiveAmountSimple() { - $testResult = 2; + $testAmountUnit = 2; + $testAmountRow = 34; + $itemProductSimple=$this->getMock( '\Magento\Quote\Model\Quote\Item', - ['getWeeeTaxAppliedAmount'], + ['getWeeeTaxAppliedAmount', 'getWeeeTaxAppliedRowAmount'], [], '', false @@ -300,28 +302,35 @@ class DataTest extends \PHPUnit_Framework_TestCase $itemProductSimple->expects($this->any()) ->method('getWeeeTaxAppliedAmount') - ->will($this->returnValue(\Zend_Json::encode($testResult))); + ->will($this->returnValue($testAmountUnit)); + $itemProductSimple->expects($this->any()) + ->method('getWeeeTaxAppliedRowAmount') + ->will($this->returnValue($testAmountRow)); - $this->assertEquals($testResult, $this->helperData->getAppliedAmount($itemProductSimple)); + $this->assertEquals($testAmountUnit, $this->helperData->getWeeeTaxAppliedAmount($itemProductSimple)); + $this->assertEquals($testAmountRow, $this->helperData->getWeeeTaxAppliedRowAmount($itemProductSimple)); } - public function getAppliedAmountBundle() + public function testGetRecursiveAmountBundle() { - $testAmount1 = 1; - $testAmount2 = 2; + $testAmountUnit1 = 1; + $testAmountUnit2 = 2; + $testTotalUnit = $testAmountUnit1 + $testAmountUnit2; - $testArray = $testAmount1 + $testAmount2; + $testAmountRow1 = 33; + $testAmountRow2 = 444; + $testTotalRow = $testAmountRow1 + $testAmountRow2; $itemProductSimple1=$this->getMock( '\Magento\Quote\Model\Quote\Item', - ['getWeeeTaxAppliedAmount'], + ['getWeeeTaxAppliedAmount', 'getWeeeTaxAppliedRowAmount'], [], '', false ); $itemProductSimple2=$this->getMock( '\Magento\Quote\Model\Quote\Item', - ['getWeeeTaxAppliedAmount'], + ['getWeeeTaxAppliedAmount', 'getWeeeTaxAppliedRowAmount'], [], '', false @@ -329,11 +338,17 @@ class DataTest extends \PHPUnit_Framework_TestCase $itemProductSimple1->expects($this->any()) ->method('getWeeeTaxAppliedAmount') - ->will($this->returnValue(\Zend_Json::encode($testAmount1))); + ->will($this->returnValue($testAmountUnit1)); + $itemProductSimple1->expects($this->any()) + ->method('getWeeeTaxAppliedRowAmount') + ->will($this->returnValue($testAmountRow1)); $itemProductSimple2->expects($this->any()) ->method('getWeeeTaxAppliedAmount') - ->will($this->returnValue(\Zend_Json::encode($testAmount2))); + ->will($this->returnValue($testAmountUnit2)); + $itemProductSimple2->expects($this->any()) + ->method('getWeeeTaxAppliedRowAmount') + ->will($this->returnValue($testAmountRow2)); $itemProductBundle=$this->getMock( '\Magento\Quote\Model\Quote\Item', @@ -352,6 +367,7 @@ class DataTest extends \PHPUnit_Framework_TestCase ->method('getChildren') ->will($this->returnValue([$itemProductSimple1, $itemProductSimple2])); - $this->assertEquals($testArray, $this->helperData->getAppliedAmount($itemProductBundle)); + $this->assertEquals($testTotalUnit, $this->helperData->getWeeeTaxAppliedAmount($itemProductBundle)); + $this->assertEquals($testTotalRow, $this->helperData->getWeeeTaxAppliedRowAmount($itemProductBundle)); } } diff --git a/app/code/Magento/Weee/Test/Unit/Pricing/Render/TaxAdjustmentTest.php b/app/code/Magento/Weee/Test/Unit/Pricing/Render/TaxAdjustmentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9671a5838e7d0a4ff074d4328c7dd76dafae671d --- /dev/null +++ b/app/code/Magento/Weee/Test/Unit/Pricing/Render/TaxAdjustmentTest.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Weee\Test\Unit\Pricing\Render; + +class TaxAdjustmentTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Weee\Pricing\Render\TaxAdjustment + */ + protected $model; + + /** + * Weee helper mock + * + * @var \Magento\Weee\Helper\Data | \PHPUnit_Framework_MockObject_MockObject + */ + protected $weeeHelperMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManager; + + /** + * Init mocks and model + */ + public function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->weeeHelperMock = $this->getMock( + 'Magento\Weee\Helper\Data', + ['typeOfDisplay'], + [], + '', + false + ); + + $this->model = $this->objectManager->getObject( + '\Magento\Weee\Pricing\Render\TaxAdjustment', + [ + 'weeeHelper' => $this->weeeHelperMock, + ] + ); + } + + /** + * Test for method getDefaultExclusions + * + * @dataProvider getDefaultExclusionsDataProvider + */ + public function testGetDefaultExclusions($weeeIsExcluded) + { + //setup + $this->weeeHelperMock->expects($this->atLeastOnce())->method('typeOfDisplay')->willReturn($weeeIsExcluded); + + //test + $defaultExclusions = $this->model->getDefaultExclusions(); + $this->assertNotEmpty($defaultExclusions, 'Expected to have at least one default exclusion: tax'); + + $taxCode = $this->model->getAdjustmentCode(); // since Weee's TaxAdjustment is a subclass of Tax's Adjustment + $this->assertContains($taxCode, $defaultExclusions); + + $weeeCode = \Magento\Weee\Pricing\Adjustment::ADJUSTMENT_CODE; + if ($weeeIsExcluded) { + $this->assertContains($weeeCode, $defaultExclusions); + } else { + $this->assertNotContains($weeeCode, $defaultExclusions); + } + } + + /** + * Data provider for testGetDefaultExclusions() + * @return array + */ + public function getDefaultExclusionsDataProvider() + { + return [ + 'weee part of exclusions' => [true], + 'weee not part of exclusions' => [false], + ]; + } +} diff --git a/app/code/Magento/Weee/view/adminhtml/templates/renderer/tax.phtml b/app/code/Magento/Weee/view/adminhtml/templates/renderer/tax.phtml index 9921f99663fca065552aa718abce5cdd53ee90ca..f7a6ec99ff40766cb70b24d742f1775c0ebe2404 100644 --- a/app/code/Magento/Weee/view/adminhtml/templates/renderer/tax.phtml +++ b/app/code/Magento/Weee/view/adminhtml/templates/renderer/tax.phtml @@ -50,7 +50,8 @@ $data = ['fptAttribute' => [ <script data-role="row-template" type="text/x-magento-template"> <tr id="<?php echo $block->getElement()->getHtmlId() ?>_weee_tax_row_<%- data.index %>" data-role="fpt-item-row"> <td class="col-website" <?php if (!$block->isMultiWebsites()): ?>style="display: none"<?php endif; ?>> - <select name="<?php /* @escapeNotVerified */ echo $block->getElement()->getName() ?>[<%- data.index %>][website_id]" + <select id="<?php /* @escapeNotVerified */ echo $block->getElement()->getName() ?>_weee_tax_row_<%- data.index %>_website" + name="<?php /* @escapeNotVerified */ echo $block->getElement()->getName() ?>[<%- data.index %>][website_id]" class="<?php /* @escapeNotVerified */ echo $block->getElement()->getClass() ?> website required-entry" data-role="select-website"> <?php foreach ($block->getWebsites() as $_websiteId => $_info): ?> <option value="<?php /* @escapeNotVerified */ echo $_websiteId ?>"><?php /* @escapeNotVerified */ echo $_info['name'] ?><?php if (!empty($_info['currency'])): ?>[<?php /* @escapeNotVerified */ echo $_info['currency'] ?>]<?php endif; ?></option> diff --git a/app/code/Magento/Weee/view/base/layout/catalog_product_prices.xml b/app/code/Magento/Weee/view/base/layout/catalog_product_prices.xml index 378427a15c62e0f3111a25555c46177b14f72746..073fd52cd2afd1a88fd7d336d058e775f8d44d8b 100644 --- a/app/code/Magento/Weee/view/base/layout/catalog_product_prices.xml +++ b/app/code/Magento/Weee/view/base/layout/catalog_product_prices.xml @@ -11,6 +11,10 @@ <argument name="default" xsi:type="array"> <item name="adjustments" xsi:type="array"> <item name="default" xsi:type="array"> + <item name="tax" xsi:type="array"> + <item name="adjustment_render_class" xsi:type="string">Magento\Weee\Pricing\Render\TaxAdjustment</item> + <item name="adjustment_render_template" xsi:type="string">Magento_Tax::pricing/adjustment.phtml</item> + </item> <item name="weee" xsi:type="array"> <item name="adjustment_render_class" xsi:type="string">Magento\Weee\Pricing\Render\Adjustment</item> <item name="adjustment_render_template" xsi:type="string">Magento_Weee::pricing/adjustment.phtml</item> diff --git a/app/code/Magento/Weee/view/frontend/templates/item/price/row.phtml b/app/code/Magento/Weee/view/frontend/templates/item/price/row.phtml index 672f7efc3361f8d36dae5bda6f0bd0d9e687122c..642b533e21539212a7cf3c6a3148ffb0f056427d 100644 --- a/app/code/Magento/Weee/view/frontend/templates/item/price/row.phtml +++ b/app/code/Magento/Weee/view/frontend/templates/item/price/row.phtml @@ -18,7 +18,7 @@ $item = $block->getItem(); <?php else: ?> <span class="cart-price"> <?php endif; ?> - <?php /* @escapeNotVerified */ echo $block->formatPrice($block->getRowDisplayPriceInclTax()); ?> + <?php /* @escapeNotVerified */ echo $block->formatPrice($block->getRowDisplayPriceInclTax()); ?> </span> <?php if ($this->helper('Magento\Weee\Helper\Data')->getApplied($item)): ?> @@ -65,7 +65,7 @@ $item = $block->getItem(); <?php if ($block->displayFinalPrice()): ?> <span class="cart-tax-total" - data-tax-toggle='{"itemTaxId" : "#esubtotal-item-tax-details<?php /* @escapeNotVerified */ echo $item->getId(); ?>"}'> + data-mage-init='{"taxToggle": {"itemTaxId" : "#esubtotal-item-tax-details<?php /* @escapeNotVerified */ echo $item->getId(); ?>"}}'> <span class="weee" data-label="<?php echo $block->escapeHtml(__('Total')); ?>"> <?php /* @escapeNotVerified */ echo $block->formatPrice($block->getFinalRowDisplayPriceExclTax()); ?> </span> diff --git a/app/code/Magento/Weee/view/frontend/templates/item/price/unit.phtml b/app/code/Magento/Weee/view/frontend/templates/item/price/unit.phtml index d95de9179d653c01b99eaa2d7619d7eada41ba98..7826d19ef83262d8c010abd3675094ba36c32c03 100644 --- a/app/code/Magento/Weee/view/frontend/templates/item/price/unit.phtml +++ b/app/code/Magento/Weee/view/frontend/templates/item/price/unit.phtml @@ -65,7 +65,7 @@ $item = $block->getItem(); <?php if ($block->displayFinalPrice()): ?> <span class="cart-tax-total" - data-mage-init='{"taxToggle": {"itemTaxId" : "#eunit-item-tax-details<?php /* @escapeNotVerified */ echo $item->getId(); ?>"}}'> + data-mage-init='{"taxToggle": {"itemTaxId" : "#eunit-item-tax-details<?php /* @escapeNotVerified */ echo $item->getId(); ?>"}}'> <span class="weee" data-label="<?php echo $block->escapeHtml(__('Total')); ?>"> <?php /* @escapeNotVerified */ echo $block->formatPrice($block->getFinalUnitDisplayPriceExclTax()); ?> </span> diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Container.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Container.php index 1b1d5a6cf68f9e53cd87bfa66b7f164ccbf32102..2a56d336ef8ab1fd7614e15d54eef3c796f0438f 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Container.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Container.php @@ -15,6 +15,15 @@ namespace Magento\Widget\Block\Adminhtml\Widget\Instance\Edit\Chooser; */ class Container extends \Magento\Framework\View\Element\Html\Select { + /**#@+ + * Frontend page layouts + */ + const PAGE_LAYOUT_1COLUMN = '1column-center'; + const PAGE_LAYOUT_2COLUMNS_LEFT = '2columns-left'; + const PAGE_LAYOUT_2COLUMNS_RIGHT = '2columns-right'; + const PAGE_LAYOUT_3COLUMNS = '3columns'; + /**#@-*/ + /** * @var \Magento\Framework\View\Layout\ProcessorFactory */ @@ -73,7 +82,10 @@ class Container extends \Magento\Framework\View\Element\Html\Select $layoutProcessor->load(); $pageLayoutProcessor = $this->_layoutProcessorFactory->create($layoutMergeParams); - $pageLayoutProcessor->addHandle($layoutProcessor->getPageLayout()); + $pageLayouts = $this->getPageLayouts(); + foreach ($pageLayouts as $pageLayout) { + $pageLayoutProcessor->addHandle($pageLayout); + } $pageLayoutProcessor->load(); $containers = array_merge($pageLayoutProcessor->getContainers(), $layoutProcessor->getContainers()); @@ -106,4 +118,19 @@ class Container extends \Magento\Framework\View\Element\Html\Select $themeCollection = $this->_themesFactory->create(); return $themeCollection->getItemById($themeId); } + + /** + * Retrieve page layouts + * + * @return array + */ + protected function getPageLayouts() + { + return [ + self::PAGE_LAYOUT_1COLUMN, + self::PAGE_LAYOUT_2COLUMNS_LEFT, + self::PAGE_LAYOUT_2COLUMNS_RIGHT, + self::PAGE_LAYOUT_3COLUMNS, + ]; + } } diff --git a/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/AbstractContainerTest.php b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/AbstractContainerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1553a9654b41a57e8da202335de598f9df99bd7c --- /dev/null +++ b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/AbstractContainerTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Widget\Test\Unit\Block\Adminhtml\Widget\Instance\Edit\Chooser; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +abstract class AbstractContainerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Framework\Event\Manager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $eventManagerMock; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $scopeConfigMock; + + /** + * @var \Magento\Backend\Block\Context|\PHPUnit_Framework_MockObject_MockObject + */ + protected $contextMock; + + /** + * @var \Magento\Theme\Model\Resource\Theme\Collection|\PHPUnit_Framework_MockObject_MockObject + */ + protected $themeCollectionMock; + + /** + * @var \Magento\Theme\Model\Resource\Theme\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $themeCollectionFactoryMock; + + /** + * @var \Magento\Theme\Model\Theme|\PHPUnit_Framework_MockObject_MockObject + */ + protected $themeMock; + + /** + * @var \Magento\Framework\View\Layout\ProcessorFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $layoutProcessorFactoryMock; + + /** + * @var \Magento\Framework\View\Model\Layout\Merge|\PHPUnit_Framework_MockObject_MockObject + */ + protected $layoutMergeMock; + + /** + * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject + */ + protected $escaperMock; + + /** + * @var ObjectManagerHelper + */ + protected $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->eventManagerMock = $this->getMockBuilder('Magento\Framework\Event\Manager') + ->setMethods(['dispatch']) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder('Magento\Framework\App\Config') + ->setMethods(['getValue']) + ->disableOriginalConstructor() + ->getMock(); + + $this->themeCollectionFactoryMock = $this->getMock( + 'Magento\Theme\Model\Resource\Theme\CollectionFactory', + ['create'], + [], + '', + false + ); + $this->themeCollectionMock = $this->getMockBuilder('Magento\Theme\Model\Resource\Theme\Collection') + ->disableOriginalConstructor() + ->setMethods(['getItemById']) + ->getMock(); + $this->themeMock = $this->getMockBuilder('Magento\Theme\Model\Theme')->disableOriginalConstructor()->getMock(); + + $this->layoutProcessorFactoryMock = $this->getMock( + 'Magento\Framework\View\Layout\ProcessorFactory', + ['create'], + [], + '', + false + ); + + $this->layoutMergeMock = $this->getMockBuilder('Magento\Framework\View\Model\Layout\Merge') + ->setMethods(['addPageHandles', 'load', 'getContainers']) + ->disableOriginalConstructor() + ->getMock(); + + $this->escaperMock = $this->getMock('Magento\Framework\Escaper', ['escapeHtml'], [], '', false); + + $this->contextMock = $this->getMockBuilder('Magento\Backend\Block\Context') + ->setMethods(['getEventManager', 'getScopeConfig', 'getEscaper']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock->expects($this->once())->method('getEventManager')->willReturn($this->eventManagerMock); + $this->contextMock->expects($this->once())->method('getScopeConfig')->willReturn($this->scopeConfigMock); + $this->contextMock->expects($this->once())->method('getEscaper')->willReturn($this->escaperMock); + } +} diff --git a/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9316be68fa739718568cf0dcc874fc881c6da6fa --- /dev/null +++ b/app/code/Magento/Widget/Test/Unit/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php @@ -0,0 +1,440 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Widget\Test\Unit\Block\Adminhtml\Widget\Instance\Edit\Chooser; + +class ContainerTest extends AbstractContainerTest +{ + /** + * @var \Magento\Widget\Block\Adminhtml\Widget\Instance\Edit\Chooser\Container + */ + protected $containerBlock; + + /** + * @return void + */ + protected function setUp() + { + parent::setUp(); + + $this->containerBlock = $this->objectManagerHelper->getObject( + 'Magento\Widget\Block\Adminhtml\Widget\Instance\Edit\Chooser\Container', + [ + 'context' => $this->contextMock, + 'themesFactory' => $this->themeCollectionFactoryMock, + 'layoutProcessorFactory' => $this->layoutProcessorFactoryMock + ] + ); + } + + /** + * @return void + */ + public function testToHtmlCatalogProductsListGroupedProduct() + { + $pageLayoutProcessorContainers = [ + 'after.body.start' => 'Page Top', + 'columns.top' => 'Before Main Columns', + 'main' => 'Main Content Container', + 'page.bottom' => 'Before Page Footer Container', + 'before.body.end' => 'Page Bottom', + 'header.container' => 'Page Header Container', + 'page.top' => 'After Page Header', + 'footer-container' => 'Page Footer Container', + 'sidebar.main' => 'Sidebar Main', + 'sidebar.additional' => 'Sidebar Additional' + ]; + $layoutProcessorContainers = [ + 'product.info.virtual.extra' => 'Product Extra Info', + 'header.panel' => 'Page Header Panel', + 'header-wrapper' => 'Page Header', + 'top.container' => 'After Page Header Top', + 'content.top' => 'Main Content Top', + 'content' => 'Main Content Area', + 'content.aside' => 'Main Content Aside', + 'content.bottom' => 'Main Content Bottom', + 'page.bottom' => 'Before Page Footer', + 'footer' => 'Page Footer', + 'cms_footer_links_container' => 'CMS Footer Links' + ]; + $allowedContainers = ['content', 'content.top', 'content.bottom']; + $expectedHtml = '<select name="block" id="" class="required-entry select" title="" ' + . 'onchange="WidgetInstance.loadSelectBoxByType(\'block_template\', this.up(\'div.group_container\'), ' + . 'this.value)"><option value="" selected="selected" >-- Please Select --</option><option value="content" >' + . 'Main Content Area</option><option value="content.bottom" >Main Content Bottom</option>' + . '<option value="content.top" >Main Content Top</option></select>'; + + $this->eventManagerMock->expects($this->once())->method('dispatch')->willReturn(true); + $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); + + $this->themeCollectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->themeCollectionMock); + $this->themeCollectionMock->expects($this->once())->method('getItemById')->willReturn($this->themeMock); + + $this->layoutProcessorFactoryMock->expects($this->exactly(2)) + ->method('create') + ->willReturn($this->layoutMergeMock); + $this->layoutMergeMock->expects($this->exactly(2))->method('addPageHandles')->willReturn(true); + $this->layoutMergeMock->expects($this->exactly(2))->method('load')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('addHandle')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('getContainers')->willReturnOnConsecutiveCalls( + $pageLayoutProcessorContainers, + $layoutProcessorContainers + ); + + $this->containerBlock->setAllowedContainers($allowedContainers); + $this->containerBlock->setValue(''); + + $this->escaperMock->expects($this->any())->method('escapeHtml')->willReturnMap( + [ + ['', null, ''], + ['-- Please Select --', null, '-- Please Select --'], + ['content', null, 'content'], + ['Main Content Area', null, 'Main Content Area'], + ['content.bottom', null, 'content.bottom'], + ['Main Content Bottom', null, 'Main Content Bottom'], + ['content.top', null, 'content.top'], + ['Main Content Top', null, 'Main Content Top'] + ] + ); + + $this->assertEquals($expectedHtml, $this->containerBlock->toHtml()); + } + + /** + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testToHtmlCatalogCategoryLinkSimpleProduct() + { + $pageLayoutProcessorContainers = [ + 'after.body.start' => 'Page Top', + 'columns.top' => 'Before Main Columns', + 'main' => 'Main Content Container', + 'page.bottom' => 'Before Page Footer Container', + 'before.body.end' => 'Page Bottom', + 'header.container' => 'Page Header Container', + 'page.top' => 'After Page Header', + 'footer-container' => 'Page Footer Container', + 'sidebar.main' => 'Sidebar Main', + 'sidebar.additional' => 'Sidebar Additional' + ]; + $layoutProcessorContainers = [ + 'product.info.simple.extra' => 'Product Extra Info', + 'header.panel' => 'Page Header Panel', + 'header-wrapper' => 'Page Header', + 'top.container' => 'After Page Header Top', + 'content.top' => 'Main Content Top', + 'content' => 'Main Content Area', + 'content.aside' => 'Main Content Aside', + 'content.bottom' => 'Main Content Bottom', + 'page.bottom' => 'Before Page Footer', + 'footer' => 'Page Footer', + 'cms_footer_links_container' => 'CMS Footer Links' + ]; + $allowedContainers = []; + $expectedHtml = '<select name="block" id="" class="required-entry select" title="" ' + . 'onchange="WidgetInstance.loadSelectBoxByType(\'block_template\', this.up(\'div.group_container\'), ' + . 'this.value)"><option value="" selected="selected" >-- Please Select --</option>' + . '<option value="page.top" >After Page Header</option><option value="top.container" >After Page Header Top' + . '</option><option value="columns.top" >Before Main Columns</option><option value="page.bottom" >' + . 'Before Page Footer</option><option value="cms_footer_links_container" >CMS Footer Links</option>' + . '<option value="content" >Main Content Area</option><option value="content.aside" >Main Content Aside' + . '</option><option value="content.bottom" >Main Content Bottom</option><option value="main" >' + . 'Main Content Container</option><option value="content.top" >Main Content Top</option>' + . '<option value="before.body.end" >Page Bottom</option><option value="footer" >Page Footer</option>' + . '<option value="footer-container" >Page Footer Container</option><option value="header-wrapper" >' + . 'Page Header</option><option value="header.container" >Page Header Container</option>' + . '<option value="header.panel" >Page Header Panel</option><option value="after.body.start" >' + . 'Page Top</option><option value="product.info.simple.extra" >Product Extra Info</option>' + . '<option value="sidebar.additional" >Sidebar Additional</option>' + . '<option value="sidebar.main" >Sidebar Main</option></select>'; + + $this->eventManagerMock->expects($this->once())->method('dispatch')->willReturn(true); + $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); + + $this->themeCollectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->themeCollectionMock); + $this->themeCollectionMock->expects($this->once())->method('getItemById')->willReturn($this->themeMock); + + $this->layoutProcessorFactoryMock->expects($this->exactly(2)) + ->method('create') + ->willReturn($this->layoutMergeMock); + $this->layoutMergeMock->expects($this->exactly(2))->method('addPageHandles')->willReturn(true); + $this->layoutMergeMock->expects($this->exactly(2))->method('load')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('addHandle')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('getContainers')->willReturnOnConsecutiveCalls( + $pageLayoutProcessorContainers, + $layoutProcessorContainers + ); + + $this->containerBlock->setAllowedContainers($allowedContainers); + $this->containerBlock->setValue(''); + + $this->escaperMock->expects($this->any())->method('escapeHtml')->willReturnMap( + [ + ['', null, ''], + ['-- Please Select --', null, '-- Please Select --'], + ['page.top', null, 'page.top'], + ['After Page Header', null, 'After Page Header'], + ['top.container', null, 'top.container'], + ['After Page Header Top', null, 'After Page Header Top'], + ['columns.top', null, 'columns.top'], + ['Before Main Columns', null, 'Before Main Columns'], + ['page.bottom', null, 'page.bottom'], + ['Before Page Footer', null, 'Before Page Footer'], + ['cms_footer_links_container', null, 'cms_footer_links_container'], + ['CMS Footer Links', null, 'CMS Footer Links'], + ['content', null, 'content'], + ['Main Content Area', null, 'Main Content Area'], + ['content.aside', null, 'content.aside'], + ['Main Content Aside', null, 'Main Content Aside'], + ['content.bottom', null, 'content.bottom'], + ['Main Content Bottom', null, 'Main Content Bottom'], + ['main', null, 'main'], + ['Main Content Container', null, 'Main Content Container'], + ['content.top', null, 'content.top'], + ['Main Content Top', null, 'Main Content Top'], + ['before.body.end', null, 'before.body.end'], + ['Page Bottom', null, 'Page Bottom'], + ['footer', null, 'footer'], + ['Page Footer', null, 'Page Footer'], + ['footer-container', null, 'footer-container'], + ['Page Footer Container', null, 'Page Footer Container'], + ['header-wrapper', null, 'header-wrapper'], + ['Page Header', null, 'Page Header'], + ['header.container', null, 'header.container'], + ['Page Header Container', null, 'Page Header Container'], + ['header.panel', null, 'header.panel'], + ['Page Header Panel', null, 'Page Header Panel'], + ['after.body.start', null, 'after.body.start'], + ['Page Top', null, 'Page Top'], + ['product.info.simple.extra', null, 'product.info.simple.extra'], + ['Product Extra Info', null, 'Product Extra Info'], + ['sidebar.additional', null, 'sidebar.additional'], + ['Sidebar Additional', null, 'Sidebar Additional'], + ['sidebar.main', null, 'sidebar.main'], + ['Sidebar Main', null, 'Sidebar Main'] + ] + ); + + $this->assertEquals($expectedHtml, $this->containerBlock->toHtml()); + } + + /** + * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testToHtmlCmsStaticBlockAllProductTypes() + { + $pageLayoutProcessorContainers = [ + 'after.body.start' => 'Page Top', + 'columns.top' => 'Before Main Columns', + 'main' => 'Main Content Container', + 'page.bottom' => 'Before Page Footer Container', + 'before.body.end' => 'Page Bottom', + 'header.container' => 'Page Header Container', + 'page.top' => 'After Page Header', + 'footer-container' => 'Page Footer Container', + 'sidebar.main' => 'Sidebar Main', + 'sidebar.additional' => 'Sidebar Additional' + ]; + $layoutProcessorContainers = [ + 'product.info.price' => 'Product info auxiliary container', + 'product.info.stock.sku' => 'Product auxiliary info', + 'alert.urls' => 'Alert Urls', + 'product.info.extrahint' => 'Product View Extra Hint', + 'product.info.social' => 'Product social links container', + 'product.review.form.fields.before' => 'Review Form Fields Before', + 'header.panel' => 'Page Header Panel', + 'header-wrapper' => 'Page Header', + 'top.container' => 'After Page Header Top', + 'content.top' => 'Main Content Top', + 'content' => 'Main Content Area', + 'content.aside' => 'Main Content Aside', + 'content.bottom' => 'Main Content Bottom', + 'page.bottom' => 'Before Page Footer', + 'footer' => 'Page Footer', + 'cms_footer_links_container' => 'CMS Footer Links' + ]; + $allowedContainers = []; + $expectedHtml = '<select name="block" id="" class="required-entry select" title="" ' + . 'onchange="WidgetInstance.loadSelectBoxByType(\'block_template\', this.up(\'div.group_container\'), ' + . 'this.value)"><option value="" selected="selected" >-- Please Select --</option>' + . '<option value="page.top" >After Page Header</option><option value="top.container" >After Page Header Top' + . '</option><option value="alert.urls" >Alert Urls</option><option value="columns.top" >Before Main Columns' + . '</option><option value="page.bottom" >Before Page Footer</option><option ' + . 'value="cms_footer_links_container" >CMS Footer Links</option><option value="content" >' + . 'Main Content Area</option><option value="content.aside" >Main Content Aside</option>' + . '<option value="content.bottom" >Main Content Bottom</option><option value="main" >Main Content Container' + . '</option><option value="content.top" >Main Content Top</option><option value="before.body.end" >' + . 'Page Bottom</option><option value="footer" >Page Footer</option><option value="footer-container" >' + . 'Page Footer Container</option><option value="header-wrapper" >Page Header</option>' + . '<option value="header.container" >Page Header Container</option><option value="header.panel" >' + . 'Page Header Panel</option><option value="after.body.start" >Page Top</option>' + . '<option value="product.info.extrahint" >Product View Extra Hint</option>' + . '<option value="product.info.stock.sku" >Product auxiliary info</option>' + . '<option value="product.info.price" >Product info auxiliary container</option>' + . '<option value="product.info.social" >Product social links container</option>' + . '<option value="product.review.form.fields.before" >Review Form Fields Before</option>' + . '<option value="sidebar.additional" >Sidebar Additional</option>' + . '<option value="sidebar.main" >Sidebar Main</option></select>'; + + $this->eventManagerMock->expects($this->once())->method('dispatch')->willReturn(true); + $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); + + $this->themeCollectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->themeCollectionMock); + $this->themeCollectionMock->expects($this->once())->method('getItemById')->willReturn($this->themeMock); + + $this->layoutProcessorFactoryMock->expects($this->exactly(2)) + ->method('create') + ->willReturn($this->layoutMergeMock); + $this->layoutMergeMock->expects($this->exactly(2))->method('addPageHandles')->willReturn(true); + $this->layoutMergeMock->expects($this->exactly(2))->method('load')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('addHandle')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('getContainers')->willReturnOnConsecutiveCalls( + $pageLayoutProcessorContainers, + $layoutProcessorContainers + ); + + $this->containerBlock->setAllowedContainers($allowedContainers); + $this->containerBlock->setValue(''); + + $this->escaperMock->expects($this->any())->method('escapeHtml')->willReturnMap( + [ + ['', null, ''], + ['-- Please Select --', null, '-- Please Select --'], + ['page.top', null, 'page.top'], + ['After Page Header', null, 'After Page Header'], + ['top.container', null, 'top.container'], + ['After Page Header Top', null, 'After Page Header Top'], + ['alert.urls', null, 'alert.urls'], + ['Alert Urls', null, 'Alert Urls'], + ['columns.top', null, 'columns.top'], + ['Before Main Columns', null, 'Before Main Columns'], + ['page.bottom', null, 'page.bottom'], + ['Before Page Footer', null, 'Before Page Footer'], + ['cms_footer_links_container', null, 'cms_footer_links_container'], + ['CMS Footer Links', null, 'CMS Footer Links'], + ['content', null, 'content'], + ['Main Content Area', null, 'Main Content Area'], + ['content.aside', null, 'content.aside'], + ['Main Content Aside', null, 'Main Content Aside'], + ['content.bottom', null, 'content.bottom'], + ['Main Content Bottom', null, 'Main Content Bottom'], + ['main', null, 'main'], + ['Main Content Container', null, 'Main Content Container'], + ['content.top', null, 'content.top'], + ['Main Content Top', null, 'Main Content Top'], + ['before.body.end', null, 'before.body.end'], + ['Page Bottom', null, 'Page Bottom'], + ['footer', null, 'footer'], + ['Page Footer', null, 'Page Footer'], + ['footer-container', null, 'footer-container'], + ['Page Footer Container', null, 'Page Footer Container'], + ['header-wrapper', null, 'header-wrapper'], + ['Page Header', null, 'Page Header'], + ['header.container', null, 'header.container'], + ['Page Header Container', null, 'Page Header Container'], + ['header.panel', null, 'header.panel'], + ['Page Header Panel', null, 'Page Header Panel'], + ['after.body.start', null, 'after.body.start'], + ['Page Top', null, 'Page Top'], + ['product.info.extrahint', null, 'product.info.extrahint'], + ['Product View Extra Hint', null, 'Product View Extra Hint'], + ['product.info.stock.sku', null, 'product.info.stock.sku'], + ['Product auxiliary info', null, 'Product auxiliary info'], + ['product.info.price', null, 'product.info.price'], + ['Product info auxiliary container', null, 'Product info auxiliary container'], + ['product.info.social', null, 'product.info.social'], + ['Product social links container', null, 'Product social links container'], + ['product.review.form.fields.before', null, 'product.review.form.fields.before'], + ['Review Form Fields Before', null, 'Review Form Fields Before'], + ['sidebar.additional', null, 'sidebar.additional'], + ['Sidebar Additional', null, 'Sidebar Additional'], + ['sidebar.main', null, 'sidebar.main'], + ['Sidebar Main', null, 'Sidebar Main'] + ] + ); + + $this->assertEquals($expectedHtml, $this->containerBlock->toHtml()); + } + + /** + * @return void + */ + public function testToHtmlOrderBySkuAllPages() + { + $pageLayoutProcessorContainers = [ + 'after.body.start' => 'Page Top', + 'columns.top' => 'Before Main Columns', + 'main' => 'Main Content Container', + 'page.bottom' => 'Before Page Footer Container', + 'before.body.end' => 'Page Bottom', + 'header.container' => 'Page Header Container', + 'page.top' => 'After Page Header', + 'footer-container' => 'Page Footer Container', + 'sidebar.main' => 'Sidebar Main', + 'sidebar.additional' => 'Sidebar Additional' + ]; + $layoutProcessorContainers = [ + 'header.panel' => 'Page Header Panel', + 'header-wrapper' => 'Page Header', + 'top.container' => 'After Page Header Top', + 'content.top' => 'Main Content Top', + 'content' => 'Main Content Area', + 'content.aside' => 'Main Content Aside', + 'content.bottom' => 'Main Content Bottom', + 'page.bottom' => 'Before Page Footer', + 'footer' => 'Page Footer', + 'cms_footer_links_container' => 'CMS Footer Links' + ]; + $allowedContainers = ['sidebar.main', 'sidebar.additional']; + $expectedHtml = '<select name="block" id="" class="required-entry select" title="" ' + . 'onchange="WidgetInstance.loadSelectBoxByType(\'block_template\', this.up(\'div.group_container\'), ' + . 'this.value)"><option value="" selected="selected" >-- Please Select --</option>' + . '<option value="sidebar.additional" >Sidebar Additional</option><option value="sidebar.main" >' + . 'Sidebar Main</option></select>'; + + $this->eventManagerMock->expects($this->once())->method('dispatch')->willReturn(true); + $this->scopeConfigMock->expects($this->once())->method('getValue')->willReturn(false); + + $this->themeCollectionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->themeCollectionMock); + $this->themeCollectionMock->expects($this->once())->method('getItemById')->willReturn($this->themeMock); + + $this->layoutProcessorFactoryMock->expects($this->exactly(2)) + ->method('create') + ->willReturn($this->layoutMergeMock); + $this->layoutMergeMock->expects($this->exactly(2))->method('addPageHandles')->willReturn(true); + $this->layoutMergeMock->expects($this->exactly(2))->method('load')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('addHandle')->willReturnSelf(); + $this->layoutMergeMock->expects($this->any())->method('getContainers')->willReturnOnConsecutiveCalls( + $pageLayoutProcessorContainers, + $layoutProcessorContainers + ); + + $this->containerBlock->setAllowedContainers($allowedContainers); + $this->containerBlock->setValue(''); + + $this->escaperMock->expects($this->any())->method('escapeHtml')->willReturnMap( + [ + ['', null, ''], + ['-- Please Select --', null, '-- Please Select --'], + ['sidebar.additional', null, 'sidebar.additional'], + ['Sidebar Additional', null, 'Sidebar Additional'], + ['sidebar.main', null, 'sidebar.main'], + ['Sidebar Main', null, 'Sidebar Main'] + ] + ); + + $this->assertEquals($expectedHtml, $this->containerBlock->toHtml()); + } +} diff --git a/app/code/Magento/Wishlist/Controller/Index/Fromcart.php b/app/code/Magento/Wishlist/Controller/Index/Fromcart.php index c17fe8022295bee6efe7ac24e41c40aad374d9b0..8ee09cc1d33b71d068ae3c15f6a4346bf272e2ee 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Fromcart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Fromcart.php @@ -5,28 +5,69 @@ */ namespace Magento\Wishlist\Controller\Index; +use Magento\Checkout\Helper\Cart as CartHelper; +use Magento\Checkout\Model\Cart as CheckoutCart; +use Magento\Customer\Model\Session; use Magento\Framework\App\Action; +use Magento\Framework\Escaper; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Exception\LocalizedException; use Magento\Wishlist\Controller\IndexInterface; use Magento\Framework\Controller\ResultFactory; +use Magento\Wishlist\Controller\WishlistProviderInterface; +use Magento\Wishlist\Helper\Data as WishlistHelper; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Fromcart extends Action\Action implements IndexInterface { /** - * @var \Magento\Wishlist\Controller\WishlistProviderInterface + * @var WishlistProviderInterface */ protected $wishlistProvider; + /** + * @var WishlistHelper + */ + protected $wishlistHelper; + + /** + * @var CheckoutCart + */ + protected $cart; + + /** + * @var CartHelper + */ + protected $cartHelper; + + /** + * @var Escaper + */ + protected $escaper; + /** * @param Action\Context $context - * @param \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider + * @param WishlistProviderInterface $wishlistProvider + * @param WishlistHelper $wishlistHelper + * @param CheckoutCart $cart + * @param CartHelper $cartHelper + * @param Escaper $escaper */ public function __construct( Action\Context $context, - \Magento\Wishlist\Controller\WishlistProviderInterface $wishlistProvider + WishlistProviderInterface $wishlistProvider, + WishlistHelper $wishlistHelper, + CheckoutCart $cart, + CartHelper $cartHelper, + Escaper $escaper ) { $this->wishlistProvider = $wishlistProvider; + $this->wishlistHelper = $wishlistHelper; + $this->cart = $cart; + $this->cartHelper = $cartHelper; + $this->escaper = $escaper; parent::__construct($context); } @@ -43,14 +84,10 @@ class Fromcart extends Action\Action implements IndexInterface if (!$wishlist) { throw new NotFoundException(__('Page not found.')); } - $itemId = (int)$this->getRequest()->getParam('item'); - - /* @var \Magento\Checkout\Model\Cart $cart */ - $cart = $this->_objectManager->get('Magento\Checkout\Model\Cart'); - $session = $this->_objectManager->get('Magento\Checkout\Model\Session'); try { - $item = $cart->getQuote()->getItemById($itemId); + $itemId = (int)$this->getRequest()->getParam('item'); + $item = $this->cart->getQuote()->getItemById($itemId); if (!$item) { throw new LocalizedException( __('The requested cart item doesn\'t exist.') @@ -59,27 +96,26 @@ class Fromcart extends Action\Action implements IndexInterface $productId = $item->getProductId(); $buyRequest = $item->getBuyRequest(); - $wishlist->addNewItem($productId, $buyRequest); - $productIds[] = $productId; - $cart->getQuote()->removeItem($itemId); - $cart->save(); - $this->_objectManager->get('Magento\Wishlist\Helper\Data')->calculate(); - $productName = $this->_objectManager->get('Magento\Framework\Escaper') - ->escapeHtml($item->getProduct()->getName()); - $wishlistName = $this->_objectManager->get('Magento\Framework\Escaper') - ->escapeHtml($wishlist->getName()); - $this->messageManager->addSuccess(__("%1 has been moved to wish list %2", $productName, $wishlistName)); + $this->cart->getQuote()->removeItem($itemId); + $this->cart->save(); + + $this->wishlistHelper->calculate(); $wishlist->save(); + + $this->messageManager->addSuccessMessage(__( + "%1 has been moved to your wish list.", + $this->escaper->escapeHtml($item->getProduct()->getName()) + )); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('We can\'t move the item to the wish list.')); + $this->messageManager->addExceptionMessage($e, __('We can\'t move the item to the wish list.')); } /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - return $resultRedirect->setUrl($this->_objectManager->get('Magento\Checkout\Helper\Cart')->getCartUrl()); + return $resultRedirect->setUrl($this->cartHelper->getCartUrl()); } } diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php new file mode 100644 index 0000000000000000000000000000000000000000..58109d6dc3d256607c2b1ba33b3101d50ede0944 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/FromcartTest.php @@ -0,0 +1,362 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Wishlist\Test\Unit\Controller\Index; + +use Magento\Checkout\Helper\Cart as CartHelper; +use Magento\Checkout\Model\Cart as CheckoutCart; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Controller\Result\Redirect as ResultRedirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\DataObject; +use Magento\Framework\Escaper; +use Magento\Framework\Message\Manager as MessageManager; +use Magento\Wishlist\Controller\Index\Fromcart; +use Magento\Wishlist\Controller\WishlistProviderInterface; +use Magento\Wishlist\Helper\Data as WishlistHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class FromcartTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Fromcart + */ + protected $controller; + + /** + * @var Context | \PHPUnit_Framework_MockObject_MockObject + */ + protected $context; + + /** + * @var WishlistProviderInterface | \PHPUnit_Framework_MockObject_MockObject + */ + protected $wishlistProvider; + + /** + * @var WishlistHelper | \PHPUnit_Framework_MockObject_MockObject + */ + protected $wishlistHelper; + + /** + * @var CheckoutCart | \PHPUnit_Framework_MockObject_MockObject + */ + protected $cart; + + /** + * @var CartHelper | \PHPUnit_Framework_MockObject_MockObject + */ + protected $cartHelper; + + /** + * @var Escaper | \PHPUnit_Framework_MockObject_MockObject + */ + protected $escaper; + + /** + * @var Http | \PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * @var MessageManager | \PHPUnit_Framework_MockObject_MockObject + */ + protected $messageManager; + + /** + * @var ResultFactory | \PHPUnit_Framework_MockObject_MockObject + */ + protected $resultFactory; + + /** + * @var ResultRedirect | \PHPUnit_Framework_MockObject_MockObject + */ + protected $resultRedirect; + + protected function setUp() + { + $this->prepareContext(); + + $this->wishlistProvider = $this->getMockBuilder('Magento\Wishlist\Controller\WishlistProviderInterface') + ->getMockForAbstractClass(); + + $this->wishlistHelper = $this->getMockBuilder('Magento\Wishlist\Helper\Data') + ->disableOriginalConstructor() + ->getMock(); + + $this->cart = $this->getMockBuilder('Magento\Checkout\Model\Cart') + ->disableOriginalConstructor() + ->getMock(); + + $this->cartHelper = $this->getMockBuilder('Magento\Checkout\Helper\Cart') + ->disableOriginalConstructor() + ->getMock(); + + $this->escaper = $this->getMockBuilder('Magento\Framework\Escaper') + ->disableOriginalConstructor() + ->getMock(); + + $this->controller = new Fromcart( + $this->context, + $this->wishlistProvider, + $this->wishlistHelper, + $this->cart, + $this->cartHelper, + $this->escaper + ); + } + + /** + * @expectedException \Magento\Framework\Exception\NotFoundException + * @expectedExceptionMessage Page not found + */ + public function testExecutePageNotFound() + { + $this->wishlistProvider->expects($this->once()) + ->method('getWishlist') + ->willReturn(null); + + $this->controller->execute(); + } + + public function testExecuteNoCartItem() + { + $itemId = 1; + $cartUrl = 'cart_url'; + + $wishlistMock = $this->getMockBuilder('Magento\Wishlist\Model\Wishlist') + ->disableOriginalConstructor() + ->getMock(); + + $this->wishlistProvider->expects($this->once()) + ->method('getWishlist') + ->willReturn($wishlistMock); + + $this->request->expects($this->once()) + ->method('getParam') + ->with('item') + ->willReturn($itemId); + + $quoteMock = $this->getMockBuilder('Magento\Quote\Model\Quote') + ->disableOriginalConstructor() + ->getMock(); + + $quoteMock->expects($this->once()) + ->method('getItemById') + ->with($itemId) + ->willReturn(null); + + $this->cart->expects($this->once()) + ->method('getQuote') + ->willReturn($quoteMock); + + $this->cartHelper->expects($this->once()) + ->method('getCartUrl') + ->willReturn($cartUrl); + + $this->messageManager->expects($this->once()) + ->method('addErrorMessage') + ->with(__('The requested cart item doesn\'t exist.')) + ->willReturnSelf(); + + $this->resultRedirect->expects($this->once()) + ->method('setUrl') + ->with($cartUrl) + ->willReturnSelf(); + + $this->assertSame($this->resultRedirect, $this->controller->execute()); + } + + public function testExecute() + { + $itemId = 1; + $cartUrl = 'cart_url'; + $productId = 1; + $productName = 'product_name'; + + $dataObjectMock = $this->getMockBuilder('Magento\Framework\DataObject') + ->disableOriginalConstructor() + ->getMock(); + + $wishlistMock = $this->getMockBuilder('Magento\Wishlist\Model\Wishlist') + ->disableOriginalConstructor() + ->getMock(); + $wishlistMock->expects($this->once()) + ->method('addNewItem') + ->with($productId, $dataObjectMock) + ->willReturnSelf(); + $wishlistMock->expects($this->once()) + ->method('save') + ->willReturnSelf(); + + $this->wishlistProvider->expects($this->once()) + ->method('getWishlist') + ->willReturn($wishlistMock); + + $this->wishlistHelper->expects($this->once()) + ->method('calculate') + ->willReturnSelf(); + + $this->request->expects($this->once()) + ->method('getParam') + ->with('item') + ->willReturn($itemId); + + $quoteMock = $this->createQuoteMock($productId, $productName, $dataObjectMock, $itemId); + + $this->cart->expects($this->exactly(2)) + ->method('getQuote') + ->willReturn($quoteMock); + $this->cart->expects($this->once()) + ->method('save') + ->willReturnSelf(); + + $this->cartHelper->expects($this->once()) + ->method('getCartUrl') + ->willReturn($cartUrl); + + $this->escaper->expects($this->once()) + ->method('escapeHtml') + ->with($productName) + ->willReturn($productName); + + $this->messageManager->expects($this->once()) + ->method('addSuccessMessage') + ->with(__("%1 has been moved to your wish list.", $productName)) + ->willReturnSelf(); + + $this->resultRedirect->expects($this->once()) + ->method('setUrl') + ->with($cartUrl) + ->willReturnSelf(); + + $this->assertSame($this->resultRedirect, $this->controller->execute()); + } + + public function testExecuteWithException() + { + $cartUrl = 'cart_url'; + $exceptionMessage = 'exception_message'; + $exception = new \Exception($exceptionMessage); + + $wishlistMock = $this->getMockBuilder('Magento\Wishlist\Model\Wishlist') + ->disableOriginalConstructor() + ->getMock(); + + $this->wishlistProvider->expects($this->once()) + ->method('getWishlist') + ->willReturn($wishlistMock); + + $this->request->expects($this->once()) + ->method('getParam') + ->with('item') + ->willThrowException($exception); + + $this->messageManager->expects($this->once()) + ->method('addExceptionMessage') + ->with($exception, __('We can\'t move the item to the wish list.')) + ->willReturnSelf(); + + $this->cartHelper->expects($this->once()) + ->method('getCartUrl') + ->willReturn($cartUrl); + + $this->resultRedirect->expects($this->once()) + ->method('setUrl') + ->with($cartUrl) + ->willReturnSelf(); + + $this->assertSame($this->resultRedirect, $this->controller->execute()); + } + + protected function prepareContext() + { + $this->request = $this->getMockBuilder('Magento\Framework\App\Request\Http') + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManager = $this->getMockBuilder('Magento\Framework\Message\Manager') + ->disableOriginalConstructor() + ->getMock(); + + $this->resultRedirect = $this->getMockBuilder('Magento\Framework\Controller\Result\Redirect') + ->disableOriginalConstructor() + ->getMock(); + + $this->resultFactory = $this->getMockBuilder('Magento\Framework\Controller\ResultFactory') + ->disableOriginalConstructor() + ->getMock(); + $this->resultFactory->expects($this->any()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->resultRedirect); + + $this->context = $this->getMockBuilder('Magento\Framework\App\Action\Context') + ->disableOriginalConstructor() + ->getMock(); + + $this->context->expects($this->any()) + ->method('getRequest') + ->willReturn($this->request); + $this->context->expects($this->any()) + ->method('getMessageManager') + ->willReturn($this->messageManager); + $this->context->expects($this->any()) + ->method('getResultFactory') + ->willReturn($this->resultFactory); + } + + /** + * @param int $productId + * @param string $productName + * @param DataObject $dataObjectMock + * @param int $itemId + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function createQuoteMock($productId, $productName, $dataObjectMock, $itemId) + { + $productMock = $this->getMockBuilder('Magento\Catalog\Model\Product') + ->disableOriginalConstructor() + ->getMock(); + $productMock->expects($this->once()) + ->method('getName') + ->willReturn($productName); + + $quoteItemMock = $this->getMockBuilder('Magento\Quote\Model\Quote\Item') + ->disableOriginalConstructor() + ->setMethods([ + 'getProductId', + 'getBuyRequest', + 'getProduct', + ]) + ->getMock(); + $quoteItemMock->expects($this->once()) + ->method('getProductId') + ->willReturn($productId); + $quoteItemMock->expects($this->once()) + ->method('getBuyRequest') + ->willReturn($dataObjectMock); + $quoteItemMock->expects($this->once()) + ->method('getProduct') + ->willReturn($productMock); + + $quoteMock = $this->getMockBuilder('Magento\Quote\Model\Quote') + ->disableOriginalConstructor() + ->getMock(); + $quoteMock->expects($this->once()) + ->method('getItemById') + ->with($itemId) + ->willReturn($quoteItemMock); + $quoteMock->expects($this->once()) + ->method('removeItem') + ->with($itemId) + ->willReturnSelf(); + + return $quoteMock; + } +} diff --git a/app/code/Magento/Wishlist/i18n/de_DE.csv b/app/code/Magento/Wishlist/i18n/de_DE.csv index 7d170fb8ee8f5702e701cfb9e889fbfdd62e6c8b..23cf75b5751db36f4440279d19055fc0a609f088 100644 --- a/app/code/Magento/Wishlist/i18n/de_DE.csv +++ b/app/code/Magento/Wishlist/i18n/de_DE.csv @@ -65,7 +65,7 @@ Message,Nachricht "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart","Kann Objekt nicht zum Warenkorb hinzufügen" "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.","E-Mail-Adressfeld darf nicht leer sein." diff --git a/app/code/Magento/Wishlist/i18n/en_US.csv b/app/code/Magento/Wishlist/i18n/en_US.csv index 032419a9e0618024eb892ffb4f2478c25bcb0e12..5bbcbde21a8085f640b14c6d1bc47cde46362289 100644 --- a/app/code/Magento/Wishlist/i18n/en_US.csv +++ b/app/code/Magento/Wishlist/i18n/en_US.csv @@ -65,7 +65,7 @@ Message,Message "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart","Cannot add item to shopping cart" "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.","Email address can't be empty." diff --git a/app/code/Magento/Wishlist/i18n/es_ES.csv b/app/code/Magento/Wishlist/i18n/es_ES.csv index 0c3d383c19eb5b00384fb9278a46f6c6a864dc28..e811032517e7ce9b5a7a5f9c2488c0f4b16eec7f 100644 --- a/app/code/Magento/Wishlist/i18n/es_ES.csv +++ b/app/code/Magento/Wishlist/i18n/es_ES.csv @@ -65,7 +65,7 @@ Message,Mensaje "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart","No se pudo añadir el artÃculo al carrito de compra" "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.","El campo Dirección de correo electrónico no puede estar vacÃo." diff --git a/app/code/Magento/Wishlist/i18n/fr_FR.csv b/app/code/Magento/Wishlist/i18n/fr_FR.csv index 393bd7ad11c2455ddb548f10a17cb532ea4c10d1..23d53490578b6701525ef258b068e6a48e738872 100644 --- a/app/code/Magento/Wishlist/i18n/fr_FR.csv +++ b/app/code/Magento/Wishlist/i18n/fr_FR.csv @@ -65,7 +65,7 @@ Message,Message "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart","Ne peut ajouter l'article au caddy" "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.","Adresse courriel ne peut être vide." diff --git a/app/code/Magento/Wishlist/i18n/nl_NL.csv b/app/code/Magento/Wishlist/i18n/nl_NL.csv index 8742b17c50cbdcfb6999186862a5f55e8c634d66..d72bfd19d242d5ee4c25146204223a4ca39add91 100644 --- a/app/code/Magento/Wishlist/i18n/nl_NL.csv +++ b/app/code/Magento/Wishlist/i18n/nl_NL.csv @@ -65,7 +65,7 @@ Message,Boodschap "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart","Kan het item niet toevoegen aan het winkelwagentje" "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.","E-mailadres mag niet leeg zijn." diff --git a/app/code/Magento/Wishlist/i18n/pt_BR.csv b/app/code/Magento/Wishlist/i18n/pt_BR.csv index 21f4da865aca77be54eddc52c8e5761342522e44..4c130bd89b6a90c0799dfcd24893f0dd6e308cb3 100644 --- a/app/code/Magento/Wishlist/i18n/pt_BR.csv +++ b/app/code/Magento/Wishlist/i18n/pt_BR.csv @@ -65,7 +65,7 @@ Message,Mensagem "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart","Não é possÃvel adicionar item ao carrinho de compras" "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.","O endereço de email não pode ficar vazio." diff --git a/app/code/Magento/Wishlist/i18n/zh_CN.csv b/app/code/Magento/Wishlist/i18n/zh_CN.csv index 5ad8d2e6066973040f76226ddae69ca8b2733964..12caf10849a5a806dac0e7ee9274bba219e259a5 100644 --- a/app/code/Magento/Wishlist/i18n/zh_CN.csv +++ b/app/code/Magento/Wishlist/i18n/zh_CN.csv @@ -65,7 +65,7 @@ Message,ä¿¡æ¯ "An error occurred while deleting the item from wish list.","An error occurred while deleting the item from wish list." "Cannot add item to shopping cart",æ— æ³•æ·»åŠ å†…å®¹åˆ°è´ç‰©è½¦ "The requested cart item doesn't exist.","The requested cart item doesn't exist." -"%1 has been moved to wish list %2","%1 has been moved to wish list %2" +"%1 has been moved to your wish list.","%1 has been moved to your wish list." "We can't move the item to the wish list.","We can't move the item to the wish list." "Message length must not exceed %1 symbols","Message length must not exceed %1 symbols" "Email address can't be empty.",邮件地å€ä¸èƒ½ä¸ºç©ºã€‚ diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less index f36869928f12c614d0ced70850ec285cf37f0625..730f3d53ff58c7e23ab424d3926e85948cf1d305 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less @@ -54,7 +54,9 @@ position: relative; text-decoration: none; transition: color .15s linear; - &:hover { + &:hover, + &:visited, + &:focus { color: @collapsible-title__hover__color; text-decoration: none; } @@ -99,7 +101,8 @@ .admin__collapsible-title { .admin__collapsible-title(); } - &.opened { + &.opened, + &._show { > .fieldset-wrapper-title { .admin__collapsible-title { &:before { diff --git a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less index 87de4578d69ff1d2b615789ffbb3813bb61f624a..308ab870acef5a280eb04a06d301ce6a1962d20e 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less @@ -11,7 +11,7 @@ } } -// Change Attribute Set +// Change Attribute Set #product_info_tabs li.removed, div.removed, .field.removed { @@ -27,3 +27,27 @@ div.removed, } } } + +// +// Product weight switcher +// --------------------------------------------- + +.weight-switcher { + .field-weight & { + .admin__control-switcher { + > .admin__field-label { + float: none; + padding-top: 7px; + text-align: left; + width: 100%; + } + } + } + .admin__control-radio[type=radio] { + margin: 0; + position: absolute; + } + .admin__control-addon { + width: auto; // IE9 width fix + } +} diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_attributes_template_popup.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_attributes_template_popup.less index 0418de00c1f4fcf0a248560f207bb241704a600f..75fd108b57db8b9628d5f921a4d58c88d8fb32eb 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_attributes_template_popup.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_attributes_template_popup.less @@ -10,4 +10,11 @@ &:extend(.abs-field-rows all); } } + .admin__field-option { + .addafter { + &:before { + margin-right: @indent__s; + } + } + } } diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_steps-wizard.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_steps-wizard.less index d4d0f23d07a1b77b8f962ddafe2a011dd85181e6..f09454d5862d66eebae899491dd4c32879e7d5c4 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_steps-wizard.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_steps-wizard.less @@ -19,12 +19,20 @@ padding: @indent__s 0; .product-create-configuration-info { float: left; - width: 50%; + width: 60%; } .product-create-configuration-actions { float: right; text-align: right; - width: 50%; + width: 40%; + &:extend(.abs-clearfix all); + } + .product-create-configuration-action { + float: right; + margin-left: @indent__s; + } + .action-menu-item { + padding-right: 0; } } diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_attribute-values.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_attribute-values.less index ea537e9c51f478981d1fe1a2ab7e933757938e94..3249ace14371737298a03515fcc12f29c63d4ff6 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_attribute-values.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_attribute-values.less @@ -37,7 +37,7 @@ border-color: @steps-wizard-attribute-entity__active__border-color; cursor: -moz-grabbing; cursor: -webkit-grabbing; - .attribute-draggable { + .draggable-handle { cursor: -moz-grabbing; cursor: -webkit-grabbing; } @@ -95,23 +95,8 @@ float: right; } - .attribute-draggable { - cursor: -moz-grab; - cursor: -webkit-grab; - cursor: move; - display: inline-block; - font-size: 0; - margin-top: -4px; - padding: 0 @indent__s 0 0; - vertical-align: middle; - - .lib-icon-font( - @icon-gripper__content, - @_icon-font: @icons-admin__font-name, - @_icon-font-size: 1.8rem, - @_icon-font-color: @image-gallery-icons__color, - @_icon-font-color-hover: @image-gallery-icons__hover__color - ); + .draggable-handle { + &:extend(.abs-draggable-handle all); } .attribute-options { diff --git a/app/design/adminhtml/Magento/backend/Magento_Downloadable/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Downloadable/web/css/source/_module.less index 3411169b6c0028f8e28a2493f6d1ee1f07b13de0..23a312161475f1ecfc7eb66d7d43f911c96a10f0 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Downloadable/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Downloadable/web/css/source/_module.less @@ -3,65 +3,136 @@ // * See COPYING.txt for license details. // */ -// -// Variables -// _____________________________________________ - -// @todo ui - rebuilt with common classes for table control and file upload ui component +.admin__field-is-downloaodable { + margin-bottom: @indent__l; +} // // File uploads dynamic control // --------------------------------------------- .downloadable-form { - .col-price, - .col-limit, - .col-share, - .col-sort { - width: 1%; + .admin__legend { + font-size: 1.8rem; + + .note { + margin-bottom: @indent__l; + } } - .col-action { - width: 1px; + + .admin__control-table { + th { + padding-bottom: 1.3rem; + vertical-align: top; + } + tbody { + tr { + &:first-child { + td { + padding-top: 0; + } + } + } + } + td { + vertical-align: top; + &.col-sort { + padding-right: 15px; + width: 15px; + } + } + } + + th { + &.col-sort { + span { + display: none; + } + } } + td { - &.col-limit { - white-space: nowrap; + &.col-actions-add { + background: @page-wrapper__background-color; + padding: 0; } } - .admin__control-table { - .admin__control-text { - margin-bottom: .5rem; - min-width: 6rem; + + .col-actions { + width: 15px; + } + + .draggable-handle { + &:extend(.abs-draggable-handle all); + margin-top: .3rem; + } + + &.admin__fieldset { // ToDo UI: remove with .admin__scope-old + .draggable-handle { + background: none; + height: inherit; + line-height: inherit; + } + } + + .action-delete { + margin-top: .4rem; + padding: 0; + &:before { + color: @color-gray60; + font-size: 1.8rem; } } - .files, - .files-wide { - .row { - margin: .7rem 0; - > .admin__control-text { - margin-top: .7rem; + + .action-add { + &:extend(.action-tertiary all); + padding: @indent__xs 0 0; + } + + .admin__field-file-url { + padding-left: 2.6rem; + .admin__field-label { + span { + display: none; } } - .uploader { - margin: .5rem 0; + .admin__field-label { + margin-left: -2.6rem; } - .fileinput-button { - color: @link__color; - cursor: pointer; - display: inline-block; - float: none; - margin: .5rem 0; - text-decoration: none; - &:hover { - color: @link__hover__color; - text-decoration: underline; - } + } + + .admin__field-uploader { + display: inline-block; + margin-left: @indent__xs; + vertical-align: top; + } + + .fileinput-button { + color: @link__color; + cursor: pointer; + display: inline-block; + &:hover { + color: @link__hover__color; + text-decoration: underline; } + } + .file-info-size { + font-size: 1.2rem; + } + + .note { + margin-top: .8rem; + } + + &.admin__fieldset { // ToDo UI: remove after admin__scope-old class removal for the product page + padding: 0; + .admin__control-radio { + position: absolute; + } } } -@-moz-document url-prefix() { // Firefox fieldset overflow bug fix +@-moz-document url-prefix() { // Firefox fieldset overflow bug fix: horizontal scroll for the table does not appear .downloadable-form { display: table-column; } diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less index 080f0e60d49481314c351aa874b5663f7d8f0b2f..50d58b39e6e259760e09a629c5f1b73dac11c1cc 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less @@ -7,6 +7,13 @@ // UI -> Data Grid // _____________________________________________ +// +// Variables +// --------------------------------------------- + +@data-grid-sticky-header__z-index: @page-actions__fixed__z-index; +@data-grid-overlay__z-index: @data-grid-sticky-header__z-index + 1; + // // Components // --------------------------------------------- @@ -32,9 +39,9 @@ .admin__data-grid-loading-mask { background: rgba(255, 255, 255, .5); bottom: 0; - left: 0; + left: -@page-content__padding-horizontal; position: absolute; - right: 0; + right: -@page-content__padding-horizontal; top: 0; z-index: @data-grid-overlay__z-index; .spinner { @@ -767,4 +774,4 @@ body._in-resize { } } } -} \ No newline at end of file +} diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/_data-grid-header.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/_data-grid-header.less index f83463ada82d3026424f9140080eddf41c6f3d77..61c9807d8e6b9d96001f9229e884526c70ca0fff 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/_data-grid-header.less +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/_data-grid-header.less @@ -17,6 +17,7 @@ @import 'data-grid-header/_data-grid-action-bookmarks.less'; @import 'data-grid-header/_data-grid-action-columns.less'; @import 'data-grid-header/_data-grid-action-export.less'; +@import 'data-grid-header/_data-grid-sticky-header.less'; .admin__data-grid-header { font-size: @font-size__base; // ToDo UI: should be deleted, added to prevent fz override with .grid diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less index 8cbd9e579ff23c15420bd925add44202e4639267..773abad71b93e9b82282af0c7b51b00f8e30ec4a 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less @@ -98,6 +98,9 @@ background-color: @data-grid-search-menu-item__background-color; } } + .data-grid-search-label { + display: none; + } } .data-grid-search-control { @@ -111,6 +114,7 @@ padding-left: @indent__base; .action-default { font-size: @data-grid-filters__font-size; + margin-bottom: 1rem; padding-left: @data-grid-filters-action__padding-left; padding-right: @data-grid-filters-action__padding-right; padding-top: @action__padding-top + .1rem; @@ -119,10 +123,8 @@ border-bottom-color: @data-grid-filters__background-color; border-right-color: @data-grid-filters-action__active__border-color; font-weight: @font-weight__semibold; - margin-left: 0; - margin-right: 0; - margin-top: -.1rem; - padding-bottom: 1.9rem; + margin: -.1rem 0 0; + padding-bottom: 1.6rem; padding-top: @action__padding-top + .2rem; position: relative; z-index: @data-grid-header__z-index - 19; @@ -138,6 +140,7 @@ } &:before { &:extend(.abs-icon all); + color: @action-dropdown__color; content: @icon-filter__content; font-size: 1.8rem; margin-right: .4rem; @@ -146,6 +149,9 @@ vertical-align: top; } } + .filters-active { + display: none; + } } // diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-sticky-header.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-sticky-header.less new file mode 100644 index 0000000000000000000000000000000000000000..0083101d59ff94c3eb1cfb61a03a64bc49724de6 --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-sticky-header.less @@ -0,0 +1,271 @@ +// /** +// * Copyright © 2015 Magento. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Variables +// --------------------------------------------- + +@data-grid-sticky-header__background-color: @color-white-fog; +@data-grid-sticky-header__border-color: @color-gray89; +@data-grid-sticky-header__box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.25); + +@data-grid-sticky-header-actions__background-color: @color-white; +@data-grid-sticky-header-actions__border-color: @action__border-color; +@data-grid-sticky-header-actions__box-shadow: @component__box-shadow__base; +@data-grid-sticky-header-actions__margin: -@indent__xs 0 0 1.1rem; +@data-grid-sticky-header-actions__padding: .6rem; +@data-grid-sticky-header-actions__z-index: 210; + +@data-grid-sticky-header-action-select__width: percentage((2 / @grid-columns)); + +// + +.sticky-header { + background-color: @data-grid-sticky-header__background-color; + border-bottom: 1px solid @data-grid-sticky-header__border-color; + box-shadow: @data-grid-sticky-header__box-shadow; + left: @page-wrapper__indent-left; + right: 0; + margin-top: -1px; + padding: @indent__xs @indent__l 0; + position: fixed; + top: 77px; + z-index: @data-grid-sticky-header__z-index; + + .admin__data-grid-wrap { + margin-bottom: 0; + overflow-x: visible; + padding-bottom: 0; + } + + .admin__data-grid-header-row { + position: relative; + text-align: right; + &:last-child { + margin: 0; + } + } + + .data-grid-search-control-wrap, + .data-grid-filters-actions-wrap, + .admin__data-grid-filters-wrap, + .admin__data-grid-pager-wrap, + .admin__data-grid-actions-wrap { + display: inline-block; + float: none; + vertical-align: top; + } + + // Action select + .action-select-wrap { + float: left; + margin-right: 1.5rem; + width: @data-grid-sticky-header-action-select__width; + } + + // Total found + .admin__control-support-text { + float: left; + } + + // Data Grid Search + .data-grid-search-control-wrap { + margin: @data-grid-sticky-header-actions__margin; + width: auto; + .data-grid-search-label { + box-sizing: border-box; + cursor: pointer; + display: block; + min-width: 3.8rem; + padding: 1.2rem @data-grid-sticky-header-actions__padding 1.7rem; + position: relative; + text-align: center; + &:before { + &:extend(.abs-icon all); + color: @action-dropdown__color; + content: @icon-search__content; + font-size: @data-grid-search-action__size; + transition: @smooth__color; + } + &:hover { + &:before { + color: @action-dropdown__hover__color; + } + } + span { + display: none; + } + } + } + + // Filters + .data-grid-filters-actions-wrap { + margin: @data-grid-sticky-header-actions__margin; + padding-left: 0; + position: relative; + .action-default { + background-color: transparent; + border: 1px solid transparent; + box-sizing: border-box; + min-width: 3.8rem; + padding: 1.2rem @data-grid-sticky-header-actions__padding 1.7rem; + text-align: center; + transition: all @appearing__transition-duration @apperaing__transition-timing-function; + span { + display: none; + } + &:before { + margin: 0; + } + &._active { + background-color: @data-grid-sticky-header-actions__background-color; + border-color: @data-grid-sticky-header-actions__border-color; + border-bottom-color: @data-grid-sticky-header-actions__background-color; + box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5); + z-index: @data-grid-sticky-header-actions__z-index; + &:after { + background-color: @data-grid-sticky-header-actions__background-color; + content: ''; + height: 6px; + left: -2px; + position: absolute; + right: -6px; + top: 100%; + } + } + } + } + + .data-grid-filters-action-wrap { + padding: 0; + } + + .admin__data-grid-filters-wrap { + background-color: @data-grid-sticky-header-actions__background-color; + border: 1px solid @data-grid-sticky-header-actions__border-color; + box-shadow: @data-grid-sticky-header__box-shadow; + left: 0; + padding-left: 3.5rem; + padding-right: 3.5rem; + position: absolute; + top: 100%; + width: 100%; + z-index: @data-grid-sticky-header-actions__z-index - 1; + } + + .admin__data-grid-filters-current { + + .admin__data-grid-filters-wrap { + &._show { + margin-top: -6px; + } + } + } + + .filters-active { + background-color: @color-phoenix-down; + border-radius: 10px; + color: @color-white; + display: block; + font-size: @font-size__base; + font-weight: @font-weight__bold; + padding: .1rem .7rem; + position: absolute; + right: -7px; + top: 0; + z-index: @data-grid-sticky-header-actions__z-index + 1; + &:empty { + padding-top: 0; + padding-bottom: 0; + } + } + + // Default view & columns + .admin__data-grid-actions-wrap { + margin: @data-grid-sticky-header-actions__margin; + padding-right: .3rem; + .admin__action-dropdown { + background-color: transparent; + box-sizing: border-box; + min-width: 3.8rem; + padding-left: @data-grid-sticky-header-actions__padding; + padding-right: @data-grid-sticky-header-actions__padding; + text-align: center; + .admin__action-dropdown-text { + display: inline-block; + min-width: 0; + max-width: 0; + overflow: hidden; + } + &:before { + margin: 0; + } + } + .admin__action-dropdown-wrap { + margin-right: 1.1rem; + } + .admin__action-dropdown-wrap, + .admin__action-dropdown { + &:after { + display: none; + } + } + ._active { + .admin__action-dropdown { + background-color: @color-white; + } + } + } + + .admin__data-grid-action-bookmarks { + .admin__action-dropdown { + &:before { + position: relative; + top: -3px; + } + } + } + + .admin__data-grid-filters-current { + border-top: 0; + border-bottom: 0; + margin-bottom: 0; + padding-bottom: 0; + padding-top: 0; + } + + .data-grid-search-control-wrap .data-grid-search-control, + .data-grid-search-control-wrap .action-submit, + .admin__data-grid-pager .admin__control-text, + .admin__data-grid-pager-wrap .admin__control-support-text { + display: none; + } + + .action-next { + margin: 0; + } + + // Table header + .data-grid { + margin-bottom: -1px; + } +} + +.data-grid-cap-left, +.data-grid-cap-right { + background-color: @data-grid-sticky-header__background-color; + bottom: -2px; + position: absolute; + top: 6rem; + width: @page-content__padding-horizontal; + z-index: @action-multicheck__z-index + 1; +} + +.data-grid-cap-left { + left: 0; +} + +.data-grid-cap-right { + right: 0; +} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_classes.less b/app/design/adminhtml/Magento/backend/web/css/source/_classes.less index ef23f9abd3aba56beb7a5f80218731c402d0081e..a3ba1fb0ccb81c3d6fbe1d0dd974f70cbe48eeb0 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_classes.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_classes.less @@ -27,3 +27,15 @@ -ms-user-select: none; user-select: none; } + +// Hidden content +.hidden { + display: none; + visibility: hidden; +} + +// Display:none content +input.no-display, +.no-display { + display: none; +} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_extends.less b/app/design/adminhtml/Magento/backend/web/css/source/_extends.less index 7893d217fbae4264ee2f193a2d298a68078888be..84563621bf9f59594e9c28f4c26f81c88182f530 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_extends.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_extends.less @@ -91,3 +91,23 @@ .extend__list-reset-styles() { &:extend(.abs-list-reset-styles all); } + +// Draggable handle with an icon +.abs-draggable-handle { + cursor: -moz-grab; + cursor: -webkit-grab; + cursor: move; + display: inline-block; + font-size: 0; + margin-top: -4px; + padding: 0 @indent__s 0 0; + vertical-align: middle; + + .lib-icon-font( + @icon-gripper__content, + @_icon-font: @icons-admin__font-name, + @_icon-font-size: 1.8rem, + @_icon-font-color: @image-gallery-icons__color, + @_icon-font-color-hover: @image-gallery-icons__hover__color + ); +} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less index e26049d26d82a179dc9be92abf2b8d7a086a70ef..907fe6e29c951d49e54f86a3d6550ac7e5d2b845 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less @@ -93,11 +93,11 @@ .action-toggle-triangle( @_dropdown__padding-right: @action-dropdown__padding-right; ); - box-shadow: none; background-color: @action-dropdown__background-color; border: 1px solid transparent; border-bottom: none; border-radius: 0; + box-shadow: none; color: @action-dropdown__color; display: inline-block; font-size: @action-dropdown__font-size; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less index b9acd7da777c525d98075eb3139a0ba779420791..ce0fee6a7723095b7771f13efd95ee082b0fd83e 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less @@ -205,7 +205,7 @@ option:empty { position: relative; z-index: 1; } - [class*='admin__control-'] { + [class*='admin__control-'][type] { .lib-css(appearence,none); .lib-vendor-prefix-flex-grow(1); box-shadow: none; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 7958cdb81d7b93ef958cc8149279592f4440503b..04ccfbf7653743769d3ad03e8968b4e7a9128858 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -291,6 +291,14 @@ } } +// Group of checkboxes or radiobuttons +.admin__field-control-group { + margin-top: .8rem; + & > .admin__field { + padding: 0; + } +} + // // Form legend // _____________________________________________ @@ -300,6 +308,7 @@ float: left; position: static; width: 100%; + &:extend(.abs-fieldset-legend); & + br { display: block; height: 0; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less index 49aa1b091bb65704b4752180589ae229a1de52e1..3914d2492ca8b423d3a59db7e734a45424700101 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_field-tooltips.less @@ -38,6 +38,9 @@ @_icon-font-line-height: 1, @_icon-font-color: @field-tooltip-icon__color ); + span { + &:extend(.abs-visually-hidden); + } } .admin__control-text:focus + .admin__field-tooltip-content, &:hover .admin__field-tooltip-content { diff --git a/app/design/adminhtml/Magento/backend/web/css/styles-old.less b/app/design/adminhtml/Magento/backend/web/css/styles-old.less index 011360c86ba188972aa5c26cb9a8e37e31a89a43..ef80afdf770123768fbecfc5f6a5f8afed5eaaa7 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -18,14 +18,6 @@ height: auto !important; overflow: visible; } -.hidden { - display: none; - visibility: hidden; -} -input.no-display, -.no-display { - display: none; -} .invisible { visibility: hidden; } @@ -132,13 +124,6 @@ input.no-display, border-width: 1px 0 1px 1px; } -// .addon {direction: rtl; } -// .addon > * {direction: ltr; } - - .addon *:focus ~ .addafter { - - } - .addon .addafter { border-width: 1px 1px 1px 0; } @@ -162,6 +147,19 @@ input.no-display, border-radius: 1px 0 0 1px; } + // For disabled field with prefix/suffix + .admin__field-control { + .admin__control-addon { + [class*='admin__control-'][type] { + &[disabled], + &:focus { + background-color: transparent; + border-color: transparent; + } + } + } + } + // // Form actions // --------------------------------------------- diff --git a/app/design/frontend/Magento/blank/Magento_Bundle/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Bundle/web/css/source/_module.less index 41f09cede4543914e6897aade0c0f088270c0537..cbc2b02bcb7c749a5a4ec1be568ef462101dd212 100644 --- a/app/design/frontend/Magento/blank/Magento_Bundle/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Bundle/web/css/source/_module.less @@ -4,120 +4,121 @@ // */ // -// Common -//-------------------------------------- +// Common +// _____________________________________________ & when (@media-common = true) { -.bundle-actions { - &:extend(.abs-box-tocart all); - .action.primary { - &:extend(.abs-button-l all); - } -} - -.bundle-options-container { - clear: both; - margin-bottom: @indent__xl; - - .legend.title { - .lib-heading(h2); + .bundle-actions { + &:extend(.abs-box-tocart all); + .action.primary { + &:extend(.abs-button-l all); + } } - .product-add-form { - display: none; - } + .bundle-options-container { + clear: both; + margin-bottom: @indent__xl; - .input-text.qty { - &:extend(.abs-input-qty all); - } + .legend.title { + .lib-heading(h2); + } - .product-options-wrapper { - margin-bottom: @indent__l; - } + .product-add-form { + display: none; + } - .action.back { - margin-bottom: @indent__l; - } + .input-text.qty { + &:extend(.abs-input-qty all); + } - .price-box { - .price { - font-weight: @font-weight__bold; - font-size: @font-size__l; + .product-options-wrapper { + margin-bottom: @indent__l; } - } - .price-notice { - &:extend(.abs-adjustment-incl-excl-tax all); - } + .action.back { + margin-bottom: @indent__l; + } - .block-bundle-summary { - &:extend(.abs-add-box-sizing all); - .lib-css(background, @secondary__color); - padding: @indent__s @indent__base; + .price-box { + .price { + font-size: @font-size__l; + font-weight: @font-weight__bold; + } + } - > .title > strong { - .lib-heading(h2); + .price-notice { + &:extend(.abs-adjustment-incl-excl-tax all); } - .bundle-summary { - margin-top: @indent__l; - &.empty { - display: none; + .block-bundle-summary { + &:extend(.abs-add-box-sizing all); + .lib-css(background, @secondary__color); + padding: @indent__s @indent__base; + + > .title > strong { + .lib-heading(h2); } - > .subtitle { - .lib-heading(h3); - display: block; + + .bundle-summary { + margin-top: @indent__l; + &.empty { + display: none; + } + > .subtitle { + .lib-heading(h3); + display: block; + } } - } - .bundle.items { - &:extend(.abs-reset-list all); - > li { - margin-bottom: @indent__s; + .bundle.items { + &:extend(.abs-reset-list all); + > li { + margin-bottom: @indent__s; + } } - } - .box-tocart { - .actions { - margin-bottom: @indent__s; - display: inline-block; + .box-tocart { + .actions { + display: inline-block; + margin-bottom: @indent__s; + } + .action.primary { + &:extend(.abs-button-l all); + } } - .action.primary { - &:extend(.abs-button-l all); + .product-addto-links { + > .action { + &:extend(.abs-action-addto-product all); + vertical-align: top; + } } } - .product-addto-links { - > .action { - &:extend(.abs-action-addto-product all); - vertical-align: top; + .nested { + .field.qty { + .label { + .lib-css(font-weight, @form-field-label__font-weight); + .lib-css(margin, 0 0 @indent__xs); + display: inline-block; + } + .lib-css(margin-top, @form-field__vertical-indent); } } - } - .nested { - .field.qty { - .label { - .lib-css(font-weight, @form-field-label__font-weight); - .lib-css(margin, 0 0 @indent__xs); - display: inline-block; - } - .lib-css(margin-top, @form-field__vertical-indent); + p.required { + .lib-css(color, @form-field-label-asterisk__color); } } - p.required { - .lib-css(color, @form-field-label-asterisk__color); - } -} } // -// Desktop -//-------------------------------------- +// Desktop +// _____________________________________________ + .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .bundle-options-container { .legend.title { - &:extend(.abs-reset-left-margin all); + &:extend(.abs-reset-left-margin-desktop all); } .bundle-options-wrapper, .product-options-wrapper { @@ -126,8 +127,8 @@ } .block-bundle-summary { float: right; - width: 40%; position: relative; + width: 40%; } .bundle-options-wrapper, .block-bundle-summary { diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_cart_index.xml b/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_cart_index.xml index 427bca31bbc005a94c7efa00dcfef5b3429ea5f1..dd89b55736f90ac540d65b0f98f468823f29a914 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_cart_index.xml +++ b/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_cart_index.xml @@ -14,9 +14,6 @@ <arguments> <argument translate="true" name="text" xsi:type="string">Summary</argument> <argument name="tag" xsi:type="string">strong</argument> - <argument name="attributes" xsi:type="array"> - <item name="title" translate="true" xsi:type="string">Summary</item> - </argument> <argument name="css_class" xsi:type="string">summary title</argument> </arguments> </block> diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_index_index.xml b/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_index_index.xml index 9e2266e17b91a79a76db3603323ddb6c98ff6777..6bd1e12ef841726a4ad5f99edb10ab232ddffb58 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_index_index.xml +++ b/app/design/frontend/Magento/blank/Magento_Checkout/layout/checkout_index_index.xml @@ -11,7 +11,6 @@ <referenceContainer name="header.panel" remove="true"/> <referenceBlock name="top.search" remove="true"/> <referenceBlock name="catalog.compare.link" remove="true"/> - <referenceBlock name="page.main.title" remove="true"/> <referenceBlock name="catalog.topnav" remove="true"/> <referenceContainer name="footer-container" remove="true"/> </body> diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout.less index 93c36249f8c5af8d803c3c8811e1a002034d80a9..66ebb311e70ac2eb167ad51a09ac3032422cee79 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout.less @@ -23,6 +23,13 @@ & when (@media-common = true) { + .checkout-index-index, + .checkout-onepage-success { + .page-title-wrapper { + &:extend(.abs-visually-hidden all); + } + } + .checkout-container { &:extend(.abs-add-clearfix all); .lib-css(margin, 0 0 @checkout-wrapper__margin); diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less index fafb2d24735897172bb04b736ae49706e1e5e778..0de27d21df32f5a65c45e23f1c67b5a09f1fd9d1 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less @@ -88,6 +88,10 @@ filter: none; } } + &._inactive { + opacity: 0.4; + filter: alpha(opacity=40); + } span { display: inline-block; padding-top: 6px; diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less index fe94af2d1f45703e5b12b7a9340327b2101dd55f..b729bbffaba3761b6dbea3132bff7664e9cd8894 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less @@ -75,8 +75,12 @@ &:not(:last-child) { margin: 0 0 @indent__base; } - > .field { - margin: 0 0 @indent__base; + } + .field { + &.cvv { + .control { + width: 40%; + } } } } @@ -191,6 +195,23 @@ } } } + .payment-method-content { + .fieldset { + > .field { + margin: 0 0 @indent__base; + &.type { + .control { + margin-left: 25.8%; + } + &.no-detection { + .control { + margin-left: 0; + } + } + } + } + } + } } .payment-method-billing-address { .action-update { diff --git a/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less index 4f2e1f6e2f20acaf528afd647c3c78ad62b6fe69..1d595ccb3fe48229cf67a0053761c1718c82ac21 100644 --- a/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less @@ -4,168 +4,171 @@ // */ // -// Common -//-------------------------------------- +// Common +// _____________________________________________ & when (@media-common = true) { -.bundle-actions { - margin: 0 0 @indent__l; - .action.primary.customize { - &:extend(.abs-button-l all); - &:extend(.abs-button-responsive all); + .bundle-actions { + margin: 0 0 @indent__l; + .action.primary.customize { + &:extend(.abs-button-l all); + &:extend(.abs-button-responsive all); + } } -} - -.bundle-options-container { - clear: both; - margin-bottom: @indent__xl; - .legend.title { - .lib-heading(h1); - border: 0; - padding: 0; - } + .bundle-options-container { + clear: both; + margin-bottom: @indent__xl; - .product-add-form { - display: none; - } + .legend.title { + .lib-heading(h1); + border: 0; + padding: 0; + } - .input-text.qty { - &:extend(.abs-input-qty all); - } + .product-add-form { + display: none; + } - .product-options-wrapper { - margin-bottom: @indent__l; - .fieldset > .field { - padding-top: @form-field__vertical-indent; - border-top: 1px @color-gray-middle1 solid; - &:first-of-type { - border-top: 0; - padding-top: 0; - } + .input-text.qty { + &:extend(.abs-input-qty all); } - .field.choice { - input { - float: left; - } - .label { - margin-left: 24px; - display: block; - } - .product-name { - display: block; - } - .label { - &:extend(.abs-add-clearfix all); + + .product-options-wrapper { + margin-bottom: @indent__l; + .fieldset > .field { + border-top: 1px @color-gray-middle1 solid; + padding-top: @form-field__vertical-indent; + &:first-of-type { + border-top: 0; + padding-top: 0; + } } - .price-notice { - float: left; - &:extend(.abs-adjustment-incl-excl-tax all); + .fieldset-bundle-options { + .field.choice { + .price-notice { + float: left; + &:extend(.abs-adjustment-incl-excl-tax all); + } + } } - .price-excluding-tax { - display: inline-block; + .field.choice { + input { + float: left; + } + .label { + display: block; + margin-left: 24px; + } + .product-name { + display: block; + } + .label { + &:extend(.abs-add-clearfix all); + } + .price-excluding-tax { + display: inline-block; + } } } - } - - .action.back { - &:extend(.abs-action-button-as-link all); - margin-bottom: @indent__l; - } - .block-bundle-summary { - > .title { - margin-bottom: 15px; - > strong { - .lib-heading(h2); - } - } - > .title, - .bundle-summary .subtitle { - border-bottom: 1px @color-gray-middle1 solid; - padding-bottom: 16px; - margin-bottom: @indent__m; - } - .price-box { - margin-bottom: @indent__base; - .price-label { - display: block; - margin-bottom: @indent__xs; - } + .action.back { + &:extend(.abs-action-button-as-link all); + margin-bottom: @indent__l; } - .bundle-summary { - margin-top: @indent__l; - &.empty { - display: none; + .block-bundle-summary { + > .title { + margin-bottom: 15px; + > strong { + .lib-heading(h2); + } } - > .subtitle { - .lib-heading(h2); - display: block; + > .title, + .bundle-summary .subtitle { + border-bottom: 1px @color-gray-middle1 solid; + margin-bottom: @indent__m; + padding-bottom: 16px; } - } - - .bundle.items { - &:extend(.abs-reset-list all); - > li { + .price-box { margin-bottom: @indent__base; + .price-label { + display: block; + margin-bottom: @indent__xs; + } } - } - .box-tocart { - &:extend(.abs-box-tocart all); - .action.primary { - &:extend(.abs-button-l all); - &:extend(.abs-button-responsive all); + .bundle-summary { + margin-top: @indent__l; + &.empty { + display: none; + } + > .subtitle { + .lib-heading(h2); + display: block; + } } - } - .product-addto-links { - text-align: center; - > .action { - &:extend(.abs-actions-addto all); - margin-right: 5%; - &.tocompare { - .lib-icon-font-symbol( - @_icon-font-content: @icon-compare-full, - @_icon-font-position: before - ); + + .bundle.items { + &:extend(.abs-reset-list all); + > li { + margin-bottom: @indent__base; } + } + .box-tocart { + &:extend(.abs-box-tocart all); + .action.primary { + &:extend(.abs-button-l all); + &:extend(.abs-button-responsive all); + } + } + .product-addto-links { + text-align: center; + > .action { + &:extend(.abs-actions-addto all); + margin-right: 5%; + &.tocompare { + .lib-icon-font-symbol( + @_icon-font-content: @icon-compare-full, + @_icon-font-position: before + ); + } + } } + .product-image-container, + .product.name, + .stock { + &:extend(.abs-visually-hidden all); + } } - .product-image-container, - .product.name, - .stock { - &:extend(.abs-visually-hidden all); + p.required { + .lib-css(color, @form-field-label-asterisk__color); } - } - p.required { - .lib-css(color, @form-field-label-asterisk__color); - } - .nested { - .field.qty { - .lib-form-field-type(@_type: block); - .label { - font-weight: @form-field-label__font-weight; - } - &:last-child { - margin-bottom: 0; + .nested { + .field.qty { + .lib-form-field-type(@_type: block); + .label { + font-weight: @form-field-label__font-weight; + } + &:last-child { + margin-bottom: 0; + } + margin-top: @form-field__vertical-indent; } - margin-top: @form-field__vertical-indent; + } + .price { + font-weight: @font-weight__semibold; } } - .price { - font-weight: @font-weight__semibold; - } -} - } // -// Desktop -//-------------------------------------- +// Desktop +// _____________________________________________ + .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .bundle-actions { .action.primary.customize { @@ -182,19 +185,19 @@ width: 57%; } .block-bundle-summary { - margin-top: 66px; - padding: @indent__s @indent__base; &:extend(.abs-add-box-sizing-desktop all); float: right; - width: 40%; + margin-top: 66px; + padding: @indent__s @indent__base; position: relative; + width: 40%; .price-box { .price-wrapper, .price-wrapper > .price { + .lib-css(color, @price-color); font-size: @price-size-desktop; - line-height: @price-size-desktop; font-weight: @font-weight__semibold; - .lib-css(color, @price-color); + line-height: @price-size-desktop; } } .price-container { @@ -214,8 +217,8 @@ } .box-tocart { .action.primary { - width: 49%; margin-right: 1%; + width: 49%; } } .product-addto-links { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less index a7d90479efe309ba80c47d15e71c8246fb75aec8..1c832e6c03daf07e366cacc32dc4785934032927 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less @@ -27,6 +27,13 @@ & when (@media-common = true) { + .checkout-index-index, + .checkout-onepage-success { + .page-title-wrapper { + &:extend(.abs-visually-hidden all); + } + } + .checkout-container { &:extend(.abs-add-clearfix all); .lib-css(margin, 0 0 @checkout-wrapper__margin); diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less new file mode 100644 index 0000000000000000000000000000000000000000..20fecb56addf7701c83570720e300ab64182fef0 --- /dev/null +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less @@ -0,0 +1,209 @@ +// /** +// * Copyright © 2015 Magento. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Variables +// _____________________________________________ + +@checkout-payment-method-title__border: @checkout-shipping-method__border; +@checkout-payment-method-title__padding: @checkout-shipping-method__padding; +@checkout-payment-method-title-mobile__padding: 15px; + +@checkout-payment-method-content__padding__xl: 22px; + +@checkout-billing-address-details__line-height: 27px; +@checkout-billing-address-details__padding: 0 0 0 23px; +@checkout-billing-address-form__max-width: @checkout-shipping-address__max-width; + +// +// Common +// _____________________________________________ + +& when (@media-common = true) { + .checkout-payment-method { + .step-title { + margin-bottom: 0; + } + .payment-method { + &:first-child { + .payment-method-title { + border-top: 0; + } + } + &._active { + .payment-method-content { + display: block; + } + } + } + + .payment-method-content { + > .actions-toolbar { + > .primary { + .action { + &.primary { + &:extend(.abs-button-l all); + } + } + } + } + } + + .payment-method-title { + .lib-css(border-top, @checkout-payment-method-title__border); + .lib-css(padding, @checkout-payment-method-title__padding 0); + margin: 0; + + .payment-icon { + display: inline-block; + margin-right: @indent__xs; + vertical-align: middle; + } + + .action-help { + display: inline-block; + margin-left: @indent__xs; + } + } + + .payment-method-content { + display: none; + .lib-css(padding, 0 0 @indent__base @checkout-payment-method-content__padding__xl); + .fieldset { + &:not(:last-child) { + margin: 0 0 @indent__base; + } + } + .field { + &.cvv { + .control { + width: 40%; + } + } + } + } + + .field-select-billing, + .billing-address-form { + .lib-css(max-width, @checkout-billing-address-form__max-width); + } + + .billing-address-same-as-shipping-block { + margin: 0 0 @indent__s; + } + + .payment-method-billing-address { + margin: 0 0 @indent__base; + + .primary { + .action-update { + margin-right: 0; + } + } + + .action-cancel { + &:extend(.abs-action-button-as-link all); + } + + .billing-address-details { + .lib-css(line-height, @checkout-billing-address-details__line-height); + .lib-css(padding, @checkout-billing-address-details__padding); + + .action-edit-address { + &:extend(.abs-action-button-as-link all); + } + } + } + + .payment-method-note { + & + .payment-method-billing-address { + margin-top: @indent__base; + } + } + + .field-select-billing { + > .label { + &:extend(.abs-visually-hidden all); + } + } + .payment-method-iframe { + background-color: transparent; + display: none; + width: 100%; + } + .no-payments-block { + margin: @indent__base 0; + } + .ccard { + .legend { + &:extend(.abs-visually-hidden all); + } + .year { + padding-left: @indent__l; + } + } + } +} + +// +// Mobile +// _____________________________________________ + +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .checkout-payment-method { + .payment-methods { + .lib-css(margin, 0 -(@checkout-payment-method-title-mobile__padding)); + } + + .payment-method-title { + .lib-css(padding, @checkout-payment-method-title-mobile__padding) + } + + .payment-method-content { + .lib-css(padding, 0 @checkout-payment-method-title-mobile__padding @indent__base); + } + + .payment-method-billing-address { + .action-cancel { + margin-top: @indent__s; + } + } + } +} + +// +// Desktop +// _____________________________________________ + +.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { + .checkout-payment-method { + .payment-methods { + .actions-toolbar { + .primary { + float: right; + margin: 0; + } + } + } + .fieldset { + > .field-select-billing { + > .control { + float: none; + width: 100%; + } + } + } + } + .payment-method-billing-address { + .action-update { + float: right; + } + .actions-toolbar { + .action-cancel { + margin: 6px @indent__base 0 0; + } + } + } +} diff --git a/app/etc/di.xml b/app/etc/di.xml index fbb7c6946ce3cb006f2be5c1d76476b3e5f05f53..e26240c8b6c8cb6200191e0c245d5f371e1b90c9 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1048,6 +1048,13 @@ </argument> </arguments> </type> + <type name="Magento\Server\Reflection" shared="false" /> + <type name="Magento\Framework\Reflection\DataObjectProcessor"> + <arguments> + <argument name="extensionAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\ExtensionAttributesProcessor\Proxy</argument> + <argument name="customAttributesProcessor" xsi:type="object">Magento\Framework\Reflection\CustomAttributesProcessor\Proxy</argument> + </arguments> + </type> <type name="Magento\Framework\Url\Decoder"> <arguments> <argument name="urlBuilder" xsi:type="object">Magento\Framework\UrlInterface</argument> diff --git a/composer.json b/composer.json index 0889646cdd9c7635d753b3f62a77a6ca8c75f96a..7548136c397c5c9bd9311082c98f201794a148c1 100644 --- a/composer.json +++ b/composer.json @@ -15,31 +15,31 @@ ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", - "zendframework/zend-stdlib": "2.4.0", - "zendframework/zend-code": "2.4.0", - "zendframework/zend-server": "2.4.0", - "zendframework/zend-soap": "2.4.0", - "zendframework/zend-uri": "2.4.0", - "zendframework/zend-validator": "2.4.0", - "zendframework/zend-crypt": "2.4.0", - "zendframework/zend-console": "2.4.0", - "zendframework/zend-modulemanager": "2.4.0", - "zendframework/zend-mvc": "2.4.0", - "zendframework/zend-text": "2.4.0", - "zendframework/zend-i18n": "2.4.0", - "zendframework/zend-eventmanager": "2.4.0", - "zendframework/zend-view": "2.4.0", - "zendframework/zend-servicemanager": "2.4.0", - "zendframework/zend-json": "2.4.0", - "zendframework/zend-config": "2.4.0", - "zendframework/zend-form": "2.4.0", - "zendframework/zend-di": "2.4.0", - "zendframework/zend-serializer": "2.4.0", - "zendframework/zend-log": "2.4.0", - "zendframework/zend-http": "2.4.0", + "zendframework/zend-stdlib": "~2.4.6", + "zendframework/zend-code": "~2.4.6", + "zendframework/zend-server": "~2.4.6", + "zendframework/zend-soap": "~2.4.6", + "zendframework/zend-uri": "~2.4.6", + "zendframework/zend-validator": "~2.4.6", + "zendframework/zend-crypt": "~2.4.6", + "zendframework/zend-console": "~2.4.6", + "zendframework/zend-modulemanager": "~2.4.6", + "zendframework/zend-mvc": "~2.4.6", + "zendframework/zend-text": "~2.4.6", + "zendframework/zend-i18n": "~2.4.6", + "zendframework/zend-eventmanager": "~2.4.6", + "zendframework/zend-view": "~2.4.6", + "zendframework/zend-servicemanager": "~2.4.6", + "zendframework/zend-json": "~2.4.6", + "zendframework/zend-config": "~2.4.6", + "zendframework/zend-form": "~2.4.6", + "zendframework/zend-di": "~2.4.6", + "zendframework/zend-serializer": "~2.4.6", + "zendframework/zend-log": "~2.4.6", + "zendframework/zend-http": "~2.4.6", "magento/zendframework1": "1.12.13", "composer/composer": "1.0.0-alpha10", - "monolog/monolog": "1.11.0", + "monolog/monolog": "1.16.0", "oyejorge/less.php": "1.7.0.3", "pelago/emogrifier": "0.1.1", "tubalmartin/cssmin": "2.4.8-p4", @@ -89,6 +89,7 @@ "magento/module-catalog-import-export": "self.version", "magento/module-catalog-inventory": "self.version", "magento/module-catalog-rule": "self.version", + "magento/module-catalog-rule-configurable": "self.version", "magento/module-catalog-search": "self.version", "magento/module-catalog-url-rewrite": "self.version", "magento/module-catalog-widget": "self.version", @@ -136,6 +137,7 @@ "magento/module-paypal": "self.version", "magento/module-persistent": "self.version", "magento/module-product-alert": "self.version", + "magento/module-product-video": "self.version", "magento/module-quote": "self.version", "magento/module-reports": "self.version", "magento/module-require-js": "self.version", diff --git a/composer.lock b/composer.lock index 5f638af51713461dbdf2da2e6ad428b7b827ab1b..265f51694fc0d0236eb5fb46762c395a778326d9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +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": "ba8186cbc63f5db144177d64249726e6", + "hash": "3544fbc9568fdcc58279fcc2de51e6ec", + "content-hash": "963e7bfad082098b3a82bfb445c272d9", "packages": [ { "name": "braintree/braintree_php", @@ -216,20 +217,20 @@ }, { "name": "magento/magento-composer-installer", - "version": "0.1.4", + "version": "0.1.5", "source": { "type": "git", "url": "https://github.com/magento/magento-composer-installer.git", - "reference": "7f03451f71e55d52c2bb07325d56a4e6df322f30" + "reference": "1b33917bfc3f4a0856276dcbe46ce35362f50fc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/7f03451f71e55d52c2bb07325d56a4e6df322f30", - "reference": "7f03451f71e55d52c2bb07325d56a4e6df322f30", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/1b33917bfc3f4a0856276dcbe46ce35362f50fc3", + "reference": "1b33917bfc3f4a0856276dcbe46ce35362f50fc3", "shasum": "" }, "require": { - "composer-plugin-api": "1.0.0" + "composer-plugin-api": "^1.0" }, "require-dev": { "composer/composer": "*@dev", @@ -288,7 +289,7 @@ "composer-installer", "magento" ], - "time": "2015-03-05 21:40:30" + "time": "2015-09-14 19:59:55" }, { "name": "magento/zendframework1", @@ -339,16 +340,16 @@ }, { "name": "monolog/monolog", - "version": "1.11.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa" + "reference": "c0c0b4bee3aabce7182876b0d912ef2595563db7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/ec3961874c43840e96da3a8a1ed20d8c73d7e5aa", - "reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c0c0b4bee3aabce7182876b0d912ef2595563db7", + "reference": "c0c0b4bee3aabce7182876b0d912ef2595563db7", "shasum": "" }, "require": { @@ -359,12 +360,15 @@ "psr/log-implementation": "1.0.0" }, "require-dev": { - "aws/aws-sdk-php": "~2.4, >2.4.8", + "aws/aws-sdk-php": "^2.4.9", "doctrine/couchdb": "~1.0@dev", "graylog2/gelf-php": "~1.0", - "phpunit/phpunit": "~3.7.0", - "raven/raven": "~0.5", - "ruflin/elastica": "0.90.*", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "raven/raven": "~0.8", + "ruflin/elastica": ">=0.90 <3.0", + "swiftmailer/swiftmailer": "~5.3", "videlalvaro/php-amqplib": "~2.4" }, "suggest": { @@ -373,6 +377,7 @@ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", "ext-mongo": "Allow sending log messages to a MongoDB server", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "php-console/php-console": "Allow sending log messages to Google Chrome", "raven/raven": "Allow sending log messages to a Sentry server", "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server", @@ -381,7 +386,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.16.x-dev" } }, "autoload": { @@ -407,7 +412,7 @@ "logging", "psr-3" ], - "time": "2014-09-30 13:30:58" + "time": "2015-08-09 17:44:44" }, { "name": "oyejorge/less.php", @@ -712,12 +717,12 @@ "target-dir": "Symfony/Component/Console", "source": { "type": "git", - "url": "https://github.com/symfony/Console.git", + "url": "https://github.com/symfony/console.git", "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", + "url": "https://api.github.com/repos/symfony/console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359", "shasum": "" }, @@ -766,16 +771,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "b58c916f1db03a611b72dd702564f30ad8fe83fa" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/b58c916f1db03a611b72dd702564f30ad8fe83fa", - "reference": "b58c916f1db03a611b72dd702564f30ad8fe83fa", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae4dcc2a8d3de98bd794167a3ccda1311597c5d9", + "reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9", "shasum": "" }, "require": { @@ -820,20 +825,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-08-24 07:13:45" + "time": "2015-09-22 13:49:29" }, { "name": "symfony/finder", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/Finder.git", - "reference": "fff4b0c362640a0ab7355e2647b3d461608e9065" + "url": "https://github.com/symfony/finder.git", + "reference": "8262ab605973afbb3ef74b945daabf086f58366f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/fff4b0c362640a0ab7355e2647b3d461608e9065", - "reference": "fff4b0c362640a0ab7355e2647b3d461608e9065", + "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f", + "reference": "8262ab605973afbb3ef74b945daabf086f58366f", "shasum": "" }, "require": { @@ -869,20 +874,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-08-26 17:56:37" + "time": "2015-09-19 19:59:23" }, { "name": "symfony/process", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/Process.git", - "reference": "f7b3f73f70a7f8f49a1c838dc3debbf054732d8e" + "url": "https://github.com/symfony/process.git", + "reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/f7b3f73f70a7f8f49a1c838dc3debbf054732d8e", - "reference": "f7b3f73f70a7f8f49a1c838dc3debbf054732d8e", + "url": "https://api.github.com/repos/symfony/process/zipball/b27c8e317922cd3cdd3600850273cf6b82b2e8e9", + "reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9", "shasum": "" }, "require": { @@ -918,7 +923,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-08-27 06:45:45" + "time": "2015-09-19 19:59:23" }, { "name": "tedivm/jshrink", @@ -1012,16 +1017,16 @@ }, { "name": "zendframework/zend-code", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-code.git", - "reference": "0ed94f842ba60cdc900c46a61bdbd7ac95a3e140" + "reference": "e3b32fa0fa358643aa6880b04944650981208c20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/0ed94f842ba60cdc900c46a61bdbd7ac95a3e140", - "reference": "0ed94f842ba60cdc900c46a61bdbd7ac95a3e140", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/e3b32fa0fa358643aa6880b04944650981208c20", + "reference": "e3b32fa0fa358643aa6880b04944650981208c20", "shasum": "" }, "require": { @@ -1042,8 +1047,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1061,20 +1066,20 @@ "code", "zf2" ], - "time": "2015-03-31 15:39:14" + "time": "2015-05-11 16:17:05" }, { "name": "zendframework/zend-config", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-config.git", - "reference": "95f3a4b3fa85d49e6f060183122de4596fa6d29d" + "reference": "6b879e54096b8e0d2290f7414c38f9a5947cb8ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/95f3a4b3fa85d49e6f060183122de4596fa6d29d", - "reference": "95f3a4b3fa85d49e6f060183122de4596fa6d29d", + "url": "https://api.github.com/repos/zendframework/zend-config/zipball/6b879e54096b8e0d2290f7414c38f9a5947cb8ac", + "reference": "6b879e54096b8e0d2290f7414c38f9a5947cb8ac", "shasum": "" }, "require": { @@ -1099,8 +1104,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1118,20 +1123,20 @@ "config", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-console", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-console.git", - "reference": "54823d9ba6f8ce39046384ee5a043b5b3d5f56d7" + "reference": "92f9c51bdc42332e63914eec892364594a9b68c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/54823d9ba6f8ce39046384ee5a043b5b3d5f56d7", - "reference": "54823d9ba6f8ce39046384ee5a043b5b3d5f56d7", + "url": "https://api.github.com/repos/zendframework/zend-console/zipball/92f9c51bdc42332e63914eec892364594a9b68c8", + "reference": "92f9c51bdc42332e63914eec892364594a9b68c8", "shasum": "" }, "require": { @@ -1150,8 +1155,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1168,20 +1173,20 @@ "console", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-crypt", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-crypt.git", - "reference": "425b7c12f2a322ad74039f8e122e0a2695bf9787" + "reference": "ec78d08abaa1a09b76b4b8161ba7d27a675e5bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/425b7c12f2a322ad74039f8e122e0a2695bf9787", - "reference": "425b7c12f2a322ad74039f8e122e0a2695bf9787", + "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/ec78d08abaa1a09b76b4b8161ba7d27a675e5bf1", + "reference": "ec78d08abaa1a09b76b4b8161ba7d27a675e5bf1", "shasum": "" }, "require": { @@ -1201,8 +1206,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1219,20 +1224,20 @@ "crypt", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-di", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-di.git", - "reference": "b9f8de081adecf71a003a569e9ba76c0a4c00bf2" + "reference": "fb578aa1e1ce9fb2dccec920f113e7db4ad44297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/b9f8de081adecf71a003a569e9ba76c0a4c00bf2", - "reference": "b9f8de081adecf71a003a569e9ba76c0a4c00bf2", + "url": "https://api.github.com/repos/zendframework/zend-di/zipball/fb578aa1e1ce9fb2dccec920f113e7db4ad44297", + "reference": "fb578aa1e1ce9fb2dccec920f113e7db4ad44297", "shasum": "" }, "require": { @@ -1252,8 +1257,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1270,20 +1275,20 @@ "di", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-escaper", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-escaper.git", - "reference": "15e5769e4fcdb4bf07ebd76500810e7070e23a97" + "reference": "13f468ff824f3c83018b90aff892a1b3201383a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/15e5769e4fcdb4bf07ebd76500810e7070e23a97", - "reference": "15e5769e4fcdb4bf07ebd76500810e7070e23a97", + "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/13f468ff824f3c83018b90aff892a1b3201383a9", + "reference": "13f468ff824f3c83018b90aff892a1b3201383a9", "shasum": "" }, "require": { @@ -1297,8 +1302,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1315,20 +1320,20 @@ "escaper", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-eventmanager", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-eventmanager.git", - "reference": "58d21c95c7005a527262fd536499195f104e83f9" + "reference": "c2c46a7a2809b74ceb66fd79f66d43f97e1747b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/58d21c95c7005a527262fd536499195f104e83f9", - "reference": "58d21c95c7005a527262fd536499195f104e83f9", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/c2c46a7a2809b74ceb66fd79f66d43f97e1747b4", + "reference": "c2c46a7a2809b74ceb66fd79f66d43f97e1747b4", "shasum": "" }, "require": { @@ -1343,8 +1348,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1361,20 +1366,20 @@ "eventmanager", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-filter", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "6d8aed2da81b62a04747346c4370562cdbe34595" + "reference": "a3711101850078b2aa69586c71897acaada2e9cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/6d8aed2da81b62a04747346c4370562cdbe34595", - "reference": "6d8aed2da81b62a04747346c4370562cdbe34595", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/a3711101850078b2aa69586c71897acaada2e9cb", + "reference": "a3711101850078b2aa69586c71897acaada2e9cb", "shasum": "" }, "require": { @@ -1398,8 +1403,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1417,40 +1422,44 @@ "filter", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-form", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-form.git", - "reference": "bca0db55718355d25c2c10fdd41a83561f1c94b3" + "reference": "779ba5da3dc040c52e632ea340462af2306c7682" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/bca0db55718355d25c2c10fdd41a83561f1c94b3", - "reference": "bca0db55718355d25c2c10fdd41a83561f1c94b3", + "url": "https://api.github.com/repos/zendframework/zend-form/zipball/779ba5da3dc040c52e632ea340462af2306c7682", + "reference": "779ba5da3dc040c52e632ea340462af2306c7682", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-inputfilter": "self.version", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-inputfilter": "~2.4.0", + "zendframework/zend-stdlib": "~2.4.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", + "ircmaxell/random-lib": "^1.1", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-captcha": "self.version", - "zendframework/zend-code": "self.version", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-validator": "self.version", - "zendframework/zend-view": "self.version", - "zendframework/zendservice-recaptcha": "*" + "zendframework/zend-cache": "~2.4.0", + "zendframework/zend-captcha": "~2.4.0", + "zendframework/zend-code": "~2.4.0", + "zendframework/zend-eventmanager": "~2.4.0", + "zendframework/zend-filter": "~2.4.0", + "zendframework/zend-i18n": "~2.4.0", + "zendframework/zend-servicemanager": "~2.4.0", + "zendframework/zend-session": "~2.4.0", + "zendframework/zend-text": "~2.4.0", + "zendframework/zend-validator": "~2.4.0", + "zendframework/zend-view": "~2.4.0", + "zendframework/zendservice-recaptcha": "~2.0" }, "suggest": { "zendframework/zend-captcha": "Zend\\Captcha component", @@ -1466,8 +1475,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1484,39 +1493,40 @@ "form", "zf2" ], - "time": "2015-03-28 20:29:18" + "time": "2015-09-09 19:11:05" }, { "name": "zendframework/zend-http", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "9c6047a0bdb3094d3ea07a215ff929cc47de4deb" + "reference": "0456267c3825f3c4b558460e0bffeb4c496e6fb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/9c6047a0bdb3094d3ea07a215ff929cc47de4deb", - "reference": "9c6047a0bdb3094d3ea07a215ff929cc47de4deb", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/0456267c3825f3c4b558460e0bffeb4c496e6fb8", + "reference": "0456267c3825f3c4b558460e0bffeb4c496e6fb8", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-loader": "self.version", - "zendframework/zend-stdlib": "self.version", - "zendframework/zend-uri": "self.version", - "zendframework/zend-validator": "self.version" + "zendframework/zend-loader": "~2.4.0", + "zendframework/zend-stdlib": "~2.4.0", + "zendframework/zend-uri": "~2.4.0", + "zendframework/zend-validator": "~2.4.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master" + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-config": "~2.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1534,20 +1544,20 @@ "http", "zf2" ], - "time": "2015-03-27 15:46:30" + "time": "2015-09-14 16:11:20" }, { "name": "zendframework/zend-i18n", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "9aebc5287373a802540d75fe5508417f866c2e52" + "reference": "f26d6ae4be3f1ac98fbb3708aafae908c38f46c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/9aebc5287373a802540d75fe5508417f866c2e52", - "reference": "9aebc5287373a802540d75fe5508417f866c2e52", + "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/f26d6ae4be3f1ac98fbb3708aafae908c38f46c8", + "reference": "f26d6ae4be3f1ac98fbb3708aafae908c38f46c8", "shasum": "" }, "require": { @@ -1580,8 +1590,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1598,33 +1608,32 @@ "i18n", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-inputfilter", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-inputfilter.git", - "reference": "4b1398f3635fae3cc5e873c5bb067274f3d10a93" + "reference": "6305e9acf7da9f5481b5266cb6e0353a96c10a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/4b1398f3635fae3cc5e873c5bb067274f3d10a93", - "reference": "4b1398f3635fae3cc5e873c5bb067274f3d10a93", + "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/6305e9acf7da9f5481b5266cb6e0353a96c10a06", + "reference": "6305e9acf7da9f5481b5266cb6e0353a96c10a06", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-filter": "self.version", - "zendframework/zend-stdlib": "self.version", - "zendframework/zend-validator": "self.version" + "zendframework/zend-filter": "~2.4.0", + "zendframework/zend-stdlib": "~2.4.0", + "zendframework/zend-validator": "~2.4.8" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "dev-master", - "zendframework/zend-servicemanager": "self.version" + "phpunit/phpunit": "^4.5", + "zendframework/zend-servicemanager": "~2.4.0" }, "suggest": { "zendframework/zend-servicemanager": "To support plugin manager support" @@ -1632,8 +1641,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1645,25 +1654,25 @@ "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-input-filter", + "homepage": "https://github.com/zendframework/zend-inputfilter", "keywords": [ "inputfilter", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-09-09 15:44:54" }, { "name": "zendframework/zend-json", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-json.git", - "reference": "2d845e151c1b9a237cf1899ac31e17fb10bd1e3f" + "reference": "1db4b878846520e619fbcdc7ce826c8563f8e839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/2d845e151c1b9a237cf1899ac31e17fb10bd1e3f", - "reference": "2d845e151c1b9a237cf1899ac31e17fb10bd1e3f", + "url": "https://api.github.com/repos/zendframework/zend-json/zipball/1db4b878846520e619fbcdc7ce826c8563f8e839", + "reference": "1db4b878846520e619fbcdc7ce826c8563f8e839", "shasum": "" }, "require": { @@ -1685,8 +1694,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1704,20 +1713,20 @@ "json", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-loader", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-loader.git", - "reference": "65de2c7a56f8eee633c6bf1cfab73e45648880d4" + "reference": "5e62c44a4d23c4e09d35fcc2a3b109c944dbdc22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/65de2c7a56f8eee633c6bf1cfab73e45648880d4", - "reference": "65de2c7a56f8eee633c6bf1cfab73e45648880d4", + "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/5e62c44a4d23c4e09d35fcc2a3b109c944dbdc22", + "reference": "5e62c44a4d23c4e09d35fcc2a3b109c944dbdc22", "shasum": "" }, "require": { @@ -1731,8 +1740,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1749,20 +1758,20 @@ "loader", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-log", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-log.git", - "reference": "002e3c810cad7e31e51c9895e9e3cb6fbd312cdd" + "reference": "f6538f4b61cdacafa47c7cef26c5c0204f2d1eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/002e3c810cad7e31e51c9895e9e3cb6fbd312cdd", - "reference": "002e3c810cad7e31e51c9895e9e3cb6fbd312cdd", + "url": "https://api.github.com/repos/zendframework/zend-log/zipball/f6538f4b61cdacafa47c7cef26c5c0204f2d1eef", + "reference": "f6538f4b61cdacafa47c7cef26c5c0204f2d1eef", "shasum": "" }, "require": { @@ -1791,8 +1800,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1811,20 +1820,20 @@ "logging", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-math", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-math.git", - "reference": "f41fe4acfd809c14f2a802d1aa45dec8fcd2cc73" + "reference": "1e7e803366fc7618a8668ce2403c932196174faa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/f41fe4acfd809c14f2a802d1aa45dec8fcd2cc73", - "reference": "f41fe4acfd809c14f2a802d1aa45dec8fcd2cc73", + "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1e7e803366fc7618a8668ce2403c932196174faa", + "reference": "1e7e803366fc7618a8668ce2403c932196174faa", "shasum": "" }, "require": { @@ -1844,8 +1853,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1862,20 +1871,20 @@ "math", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-modulemanager", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-modulemanager.git", - "reference": "af7ae3cd29a1efb73cc66ae1081e606039d5c20f" + "reference": "49c0713c2b560dd434aa36b83df4c89f51f8dab4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/af7ae3cd29a1efb73cc66ae1081e606039d5c20f", - "reference": "af7ae3cd29a1efb73cc66ae1081e606039d5c20f", + "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/49c0713c2b560dd434aa36b83df4c89f51f8dab4", + "reference": "49c0713c2b560dd434aa36b83df4c89f51f8dab4", "shasum": "" }, "require": { @@ -1902,8 +1911,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -1920,50 +1929,51 @@ "modulemanager", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-mvc", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-mvc.git", - "reference": "0b4a4a829b30be510a3f215c4ff00c703ee8b431" + "reference": "5ecf513a82fe9fdeee84919eee45e8098639df04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/0b4a4a829b30be510a3f215c4ff00c703ee8b431", - "reference": "0b4a4a829b30be510a3f215c4ff00c703ee8b431", + "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/5ecf513a82fe9fdeee84919eee45e8098639df04", + "reference": "5ecf513a82fe9fdeee84919eee45e8098639df04", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-form": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-eventmanager": "~2.4.0", + "zendframework/zend-form": "~2.4.8", + "zendframework/zend-servicemanager": "~2.4.0", + "zendframework/zend-stdlib": "~2.4.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-authentication": "self.version", - "zendframework/zend-console": "self.version", - "zendframework/zend-di": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-http": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-inputfilter": "self.version", - "zendframework/zend-json": "self.version", - "zendframework/zend-log": "self.version", - "zendframework/zend-modulemanager": "self.version", - "zendframework/zend-serializer": "self.version", - "zendframework/zend-session": "self.version", - "zendframework/zend-text": "self.version", - "zendframework/zend-uri": "self.version", - "zendframework/zend-validator": "self.version", - "zendframework/zend-version": "self.version", - "zendframework/zend-view": "self.version" + "zendframework/zend-authentication": "~2.4.0", + "zendframework/zend-cache": "~2.4.0", + "zendframework/zend-console": "~2.4.0", + "zendframework/zend-di": "~2.4.0", + "zendframework/zend-filter": "~2.4.0", + "zendframework/zend-http": "~2.4.8", + "zendframework/zend-i18n": "~2.4.0", + "zendframework/zend-inputfilter": "~2.4.8", + "zendframework/zend-json": "~2.4.0", + "zendframework/zend-log": "~2.4.0", + "zendframework/zend-modulemanager": "~2.4.0", + "zendframework/zend-serializer": "~2.4.0", + "zendframework/zend-session": "~2.4.0", + "zendframework/zend-text": "~2.4.0", + "zendframework/zend-uri": "~2.4.0", + "zendframework/zend-validator": "~2.4.8", + "zendframework/zend-version": "~2.4.0", + "zendframework/zend-view": "~2.4.0" }, "suggest": { "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", @@ -1989,8 +1999,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2007,20 +2017,20 @@ "mvc", "zf2" ], - "time": "2015-03-26 18:55:14" + "time": "2015-09-14 16:32:50" }, { "name": "zendframework/zend-serializer", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-serializer.git", - "reference": "3c531789a9882a5deb721356a7bd2642b65d4b09" + "reference": "31a0da5c09f54fe76bc4e145e348b0d3d277aaf0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/3c531789a9882a5deb721356a7bd2642b65d4b09", - "reference": "3c531789a9882a5deb721356a7bd2642b65d4b09", + "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/31a0da5c09f54fe76bc4e145e348b0d3d277aaf0", + "reference": "31a0da5c09f54fe76bc4e145e348b0d3d277aaf0", "shasum": "" }, "require": { @@ -2041,8 +2051,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2060,20 +2070,20 @@ "serializer", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-server", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-server.git", - "reference": "d11ff0bd529d202022823d4accf5983cbd50fc49" + "reference": "dd6da0d3c304cb43d3ac0be2dc9c334372d3734c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/d11ff0bd529d202022823d4accf5983cbd50fc49", - "reference": "d11ff0bd529d202022823d4accf5983cbd50fc49", + "url": "https://api.github.com/repos/zendframework/zend-server/zipball/dd6da0d3c304cb43d3ac0be2dc9c334372d3734c", + "reference": "dd6da0d3c304cb43d3ac0be2dc9c334372d3734c", "shasum": "" }, "require": { @@ -2089,8 +2099,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2107,20 +2117,20 @@ "server", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-servicemanager", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "57cf99fa5ac08c05a135a8d0d676c52a5e450083" + "reference": "855294e12771b4295c26446b6ed2df2f1785f234" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/57cf99fa5ac08c05a135a8d0d676c52a5e450083", - "reference": "57cf99fa5ac08c05a135a8d0d676c52a5e450083", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/855294e12771b4295c26446b6ed2df2f1785f234", + "reference": "855294e12771b4295c26446b6ed2df2f1785f234", "shasum": "" }, "require": { @@ -2139,8 +2149,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2157,20 +2167,20 @@ "servicemanager", "zf2" ], - "time": "2015-03-23 18:29:14" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-soap", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-soap.git", - "reference": "a599463aba97ce247faf3fb443e3c7858b46449b" + "reference": "743ab449c4d0d03cee21db743c5aed360be49d36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/a599463aba97ce247faf3fb443e3c7858b46449b", - "reference": "a599463aba97ce247faf3fb443e3c7858b46449b", + "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/743ab449c4d0d03cee21db743c5aed360be49d36", + "reference": "743ab449c4d0d03cee21db743c5aed360be49d36", "shasum": "" }, "require": { @@ -2191,8 +2201,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2209,20 +2219,20 @@ "soap", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-stdlib", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "cf05c5ba75606e47ffee91cedc72778da46f74c3" + "reference": "d8ecb629a72da9f91bd95c5af006384823560b42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cf05c5ba75606e47ffee91cedc72778da46f74c3", - "reference": "cf05c5ba75606e47ffee91cedc72778da46f74c3", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/d8ecb629a72da9f91bd95c5af006384823560b42", + "reference": "d8ecb629a72da9f91bd95c5af006384823560b42", "shasum": "" }, "require": { @@ -2246,8 +2256,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2264,20 +2274,20 @@ "stdlib", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-07-21 13:55:46" }, { "name": "zendframework/zend-text", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-text.git", - "reference": "d962ea25647b20527f3ca34ae225bbc885dabfc7" + "reference": "bd2393fa1f88b719be033a07fdff23f5fa745ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/d962ea25647b20527f3ca34ae225bbc885dabfc7", - "reference": "d962ea25647b20527f3ca34ae225bbc885dabfc7", + "url": "https://api.github.com/repos/zendframework/zend-text/zipball/bd2393fa1f88b719be033a07fdff23f5fa745ad5", + "reference": "bd2393fa1f88b719be033a07fdff23f5fa745ad5", "shasum": "" }, "require": { @@ -2293,8 +2303,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2311,26 +2321,26 @@ "text", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-05-07 14:55:31" }, { "name": "zendframework/zend-uri", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-uri.git", - "reference": "bd9e625639415376f6a82551c73328448d7bc7d1" + "reference": "33512866d20cc4bc54a0c1a6a0bdfcf5088939b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/bd9e625639415376f6a82551c73328448d7bc7d1", - "reference": "bd9e625639415376f6a82551c73328448d7bc7d1", + "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/33512866d20cc4bc54a0c1a6a0bdfcf5088939b3", + "reference": "33512866d20cc4bc54a0c1a6a0bdfcf5088939b3", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-escaper": "self.version", - "zendframework/zend-validator": "self.version" + "zendframework/zend-escaper": "~2.4.0", + "zendframework/zend-validator": "~2.4.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -2340,8 +2350,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2359,37 +2369,38 @@ "uri", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-09-14 16:17:10" }, { "name": "zendframework/zend-validator", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "45fac2545a0f2eb66d71cb7966feee481e7c475f" + "reference": "81415511fe729e6de19a61936313cef43c80d337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/45fac2545a0f2eb66d71cb7966feee481e7c475f", - "reference": "45fac2545a0f2eb66d71cb7966feee481e7c475f", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/81415511fe729e6de19a61936313cef43c80d337", + "reference": "81415511fe729e6de19a61936313cef43c80d337", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "zendframework/zend-stdlib": "~2.4.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-db": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-math": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-session": "self.version", - "zendframework/zend-uri": "self.version" + "zendframework/zend-config": "~2.4.0", + "zendframework/zend-db": "~2.4.0", + "zendframework/zend-filter": "~2.4.0", + "zendframework/zend-i18n": "~2.4.0", + "zendframework/zend-math": "~2.4.0", + "zendframework/zend-servicemanager": "~2.4.0", + "zendframework/zend-session": "~2.4.0", + "zendframework/zend-uri": "~2.4.0" }, "suggest": { "zendframework/zend-db": "Zend\\Db component", @@ -2404,8 +2415,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2423,20 +2434,20 @@ "validator", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-09-08 21:04:17" }, { "name": "zendframework/zend-view", - "version": "2.4.0", + "version": "2.4.8", "source": { "type": "git", "url": "https://github.com/zendframework/zend-view.git", - "reference": "37beb1ad46e530f627b4b6c3716efd728e976ba9" + "reference": "d81da0d932a0e35f8cbc6f10d80c8b4ab54973ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/37beb1ad46e530f627b4b6c3716efd728e976ba9", - "reference": "37beb1ad46e530f627b4b6c3716efd728e976ba9", + "url": "https://api.github.com/repos/zendframework/zend-view/zipball/d81da0d932a0e35f8cbc6f10d80c8b4ab54973ea", + "reference": "d81da0d932a0e35f8cbc6f10d80c8b4ab54973ea", "shasum": "" }, "require": { @@ -2481,8 +2492,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev", - "dev-develop": "2.4-dev" + "dev-master": "2.4-dev", + "dev-develop": "2.5-dev" } }, "autoload": { @@ -2500,7 +2511,7 @@ "view", "zf2" ], - "time": "2015-03-25 20:55:48" + "time": "2015-06-16 15:22:37" } ], "packages-dev": [ @@ -2663,16 +2674,16 @@ }, { "name": "lusitanian/oauth", - "version": "v0.4.1", + "version": "v0.6.0", "source": { "type": "git", "url": "https://github.com/Lusitanian/PHPoAuthLib.git", - "reference": "b617831ffe58564c3ae06772a8b20310929af2ea" + "reference": "74a5a62457568176d9c1af41a900dd3389b70414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/b617831ffe58564c3ae06772a8b20310929af2ea", - "reference": "b617831ffe58564c3ae06772a8b20310929af2ea", + "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/74a5a62457568176d9c1af41a900dd3389b70414", + "reference": "74a5a62457568176d9c1af41a900dd3389b70414", "shasum": "" }, "require": { @@ -2726,7 +2737,7 @@ "oauth", "security" ], - "time": "2015-09-09 23:42:55" + "time": "2015-09-24 00:18:45" }, { "name": "pdepend/pdepend", @@ -2769,28 +2780,25 @@ }, { "name": "phpmd/phpmd", - "version": "2.2.3", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpmd/phpmd.git", - "reference": "5eeb5a4d39c8304910b33ae49f8813905346cc35" + "reference": "08b5bcd454a7148579b68931fc500d824afd3bb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/5eeb5a4d39c8304910b33ae49f8813905346cc35", - "reference": "5eeb5a4d39c8304910b33ae49f8813905346cc35", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/08b5bcd454a7148579b68931fc500d824afd3bb5", + "reference": "08b5bcd454a7148579b68931fc500d824afd3bb5", "shasum": "" }, "require": { "pdepend/pdepend": "~2.0", - "php": ">=5.3.0", - "symfony/config": ">=2.4", - "symfony/dependency-injection": ">=2.4", - "symfony/filesystem": ">=2.4" + "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "*", - "squizlabs/php_codesniffer": "*" + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.0" }, "bin": [ "src/bin/phpmd" @@ -2810,12 +2818,18 @@ "name": "Manuel Pichler", "email": "github@manuel-pichler.de", "homepage": "https://github.com/manuelpichler", - "role": "Project founder" + "role": "Project Founder" }, { "name": "Other contributors", "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", "role": "Contributors" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" } ], "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", @@ -2827,20 +2841,20 @@ "phpmd", "pmd" ], - "time": "2015-05-27 18:16:57" + "time": "2015-09-24 14:37:49" }, { "name": "phpunit/php-code-coverage", - "version": "2.2.2", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c" + "reference": "ef1ca6835468857944d5c3b48fa503d5554cff2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c", - "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef1ca6835468857944d5c3b48fa503d5554cff2f", + "reference": "ef1ca6835468857944d5c3b48fa503d5554cff2f", "shasum": "" }, "require": { @@ -2889,7 +2903,7 @@ "testing", "xunit" ], - "time": "2015-08-04 03:42:39" + "time": "2015-09-14 06:51:16" }, { "name": "phpunit/php-file-iterator", @@ -3020,16 +3034,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.6", + "version": "1.4.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b" + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b", - "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", "shasum": "" }, "require": { @@ -3065,7 +3079,7 @@ "keywords": [ "tokenizer" ], - "time": "2015-08-16 08:51:00" + "time": "2015-09-15 10:49:45" }, { "name": "phpunit/phpunit", @@ -3647,16 +3661,16 @@ }, { "name": "symfony/config", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/Config.git", - "reference": "5ab9ff48b3cb5b40951a607f77fc1cbfd29edba8" + "url": "https://github.com/symfony/config.git", + "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Config/zipball/5ab9ff48b3cb5b40951a607f77fc1cbfd29edba8", - "reference": "5ab9ff48b3cb5b40951a607f77fc1cbfd29edba8", + "url": "https://api.github.com/repos/symfony/config/zipball/9698fdf0a750d6887d5e7729d5cf099765b20e61", + "reference": "9698fdf0a750d6887d5e7729d5cf099765b20e61", "shasum": "" }, "require": { @@ -3693,20 +3707,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2015-08-27 06:45:45" + "time": "2015-09-21 15:02:29" }, { "name": "symfony/dependency-injection", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/DependencyInjection.git", - "reference": "c0a3a97b9450d77cd8eff81c5825efb3624c255b" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "422c3819b110f610d79c6f1dc38af23787dc790e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/DependencyInjection/zipball/c0a3a97b9450d77cd8eff81c5825efb3624c255b", - "reference": "c0a3a97b9450d77cd8eff81c5825efb3624c255b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/422c3819b110f610d79c6f1dc38af23787dc790e", + "reference": "422c3819b110f610d79c6f1dc38af23787dc790e", "shasum": "" }, "require": { @@ -3753,20 +3767,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2015-08-24 07:16:32" + "time": "2015-09-15 08:30:42" }, { "name": "symfony/filesystem", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/Filesystem.git", - "reference": "f079e9933799929584200b9a926f72f29e291654" + "url": "https://github.com/symfony/filesystem.git", + "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Filesystem/zipball/f079e9933799929584200b9a926f72f29e291654", - "reference": "f079e9933799929584200b9a926f72f29e291654", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab", + "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab", "shasum": "" }, "require": { @@ -3802,20 +3816,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2015-08-27 07:03:44" + "time": "2015-09-09 17:42:36" }, { "name": "symfony/stopwatch", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/Stopwatch.git", - "reference": "abc61bac76fb10ffa2c6373d7932bc35190dbf3b" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "08dd97b3f22ab9ee658cd16e6758f8c3c404336e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/abc61bac76fb10ffa2c6373d7932bc35190dbf3b", - "reference": "abc61bac76fb10ffa2c6373d7932bc35190dbf3b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/08dd97b3f22ab9ee658cd16e6758f8c3c404336e", + "reference": "08dd97b3f22ab9ee658cd16e6758f8c3c404336e", "shasum": "" }, "require": { @@ -3851,20 +3865,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2015-08-24 07:13:45" + "time": "2015-09-22 13:49:29" }, { "name": "symfony/yaml", - "version": "v2.7.4", + "version": "v2.7.5", "source": { "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661" + "url": "https://github.com/symfony/yaml.git", + "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/2dc7b06c065df96cc686c66da2705e5e18aef661", - "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661", + "url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770", + "reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770", "shasum": "" }, "require": { @@ -3900,7 +3914,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-08-24 07:13:45" + "time": "2015-09-14 14:14:09" } ], "aliases": [], diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php b/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php index b8f7c54ad70f11f951c24391b0efedb099ca198e..831becca6f3d2e645e9908f29bd7485fd339c50a 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php @@ -18,7 +18,7 @@ class WebapiDocBlock extends \Magento\TestFramework\Bootstrap\DocBlock protected function _getSubscribers(\Magento\TestFramework\Application $application) { $subscribers = parent::_getSubscribers($application); - array_unshift($subscribers, new \Magento\TestFramework\Annotation\ApiDataFixture($this->_fixturesBaseDir)); + $subscribers[] = new \Magento\TestFramework\Annotation\ApiDataFixture($this->_fixturesBaseDir); return $subscribers; } } diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/CartItemRepositoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9cb6aebe8099e0f566fb09382288dd1b5664eb54 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/CartItemRepositoryTest.php @@ -0,0 +1,207 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Bundle\Api; + +use Magento\TestFramework\TestCase\WebapiAbstract; + +class CartItemRepositoryTest extends WebapiAbstract +{ + const SERVICE_VERSION = 'V1'; + const SERVICE_NAME = 'quoteCartItemRepositoryV1'; + const RESOURCE_PATH = '/V1/carts/'; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_bundle_and_options.php + */ + public function testGetAll() + { + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote')->load( + 'test_order_bundle', + 'reserved_order_id' + ); + $quoteId = $quote->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $quoteId . '/items', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'GetList', + ], + ]; + $response = $this->_webApiCall($serviceInfo, ['cartId' => $quoteId]); + $this->assertEquals(1, count($response)); + $response = $response[0]; + $bundleOption = $quote->getItemById($response['item_id'])->getBuyRequest()->getBundleOption(); + $bundleOptionQty = $quote->getItemById($response['item_id'])->getBuyRequest()->getBundleOptionQty(); + $actualOptions = $response['product_option']['extension_attributes']['bundle_options']; + + $this->assertEquals(array_keys($bundleOption), array_column($actualOptions, 'option_id')); + $this->assertEquals($bundleOptionQty, array_column($actualOptions, 'option_qty', 'option_id')); + foreach ($actualOptions as $option) { + $id = $option['option_id']; + $expectedSelections = is_array($bundleOption[$id]) ? $bundleOption[$id] : [$bundleOption[$id]]; + $this->assertEquals($expectedSelections, $option['option_selections']); + } + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + * @magentoApiDataFixture Magento/Bundle/_files/product.php + */ + public function testAddItem() + { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create('Magento\Catalog\Model\Product')->load(3); + $quote = $this->objectManager->create('Magento\Quote\Model\Quote')->load( + 'test_order_item_with_items', + 'reserved_order_id' + ); + + $itemQty = 1; + $bundleProductOptions = $product->getExtensionAttributes()->getBundleProductOptions(); + $bundleOptionId = $bundleProductOptions[0]->getId(); + $optionSelections = $bundleProductOptions[0]->getProductLinks()[0]->getId(); + $buyRequest = [ + 'bundle_option' => [$bundleOptionId => [$optionSelections]], + 'bundle_option_qty' => [$bundleOptionId => 1], + 'qty' => $itemQty, + 'original_qty' => $itemQty + ]; + + $productSku = $product->getSku(); + $productId = $product->getId(); + /** @var \Magento\Quote\Model\Quote $quote */ + + $cartId = $quote->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $requestData = [ + "cartItem" => [ + "sku" => $productSku, + "qty" => $itemQty, + "quote_id" => $cartId, + "product_option" => [ + "extension_attributes" => [ + "bundle_options" => [ + [ + "option_id" => (int)$bundleOptionId, + "option_qty" => $itemQty, + "option_selections" => [(int)$optionSelections] + ] + ] + ] + ] + ] + ]; + $response = $this->_webApiCall($serviceInfo, $requestData); + $this->assertTrue($quote->hasProductId($productId)); + $this->assertEquals($buyRequest, $quote->getItemById($response['item_id'])->getBuyRequest()->getData()); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_bundle_and_options.php + */ + public function testUpdate() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote')->load( + 'test_order_bundle', + 'reserved_order_id' + ); + $cartId = $quote->getId(); + $cartItem = $quote->getAllVisibleItems()[0]; + $itemSku = $cartItem->getSku(); + $itemId = $cartItem->getId(); + + $product = $cartItem->getProduct(); + /** @var $typeInstance \Magento\Bundle\Model\Product\Type */ + $typeInstance = $product->getTypeInstance(); + $typeInstance->setStoreFilter($product->getStoreId(), $product); + $optionCollection = $typeInstance->getOptionsCollection($product); + $bundleOptions = []; + /** @var $option \Magento\Bundle\Model\Option */ + foreach ($optionCollection as $option) { + if (!$option->getRequired()) { + continue; + } + $selectionsCollection = $typeInstance->getSelectionsCollection([$option->getId()], $product); + $option = ['option_id' => $option->getId(), 'option_qty' => 1]; + $option['option_selections'] = [$selectionsCollection->getLastItem()->getSelectionId()]; + $bundleOptions[] = $option; + } + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items/' . $itemId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + $requestData = [ + "cartItem" => [ + "sku" => $itemSku, + "qty" => 2, + "quote_id" => $cartId, + "item_id" => $itemId, + "product_option" => [ + "extension_attributes" => [ + "bundle_options" => $bundleOptions + ] + ] + ] + ]; + $this->_webApiCall($serviceInfo, $requestData); + + + $quoteUpdated = $this->objectManager->create('Magento\Quote\Model\Quote')->load( + 'test_order_bundle', + 'reserved_order_id' + ); + $cartItems = $quoteUpdated->getAllVisibleItems(); + $buyRequest = $cartItems[0]->getBuyRequest()->toArray(); + + $this->assertEquals(1, count($cartItems)); + $this->assertEquals(count($buyRequest['bundle_option']), count($bundleOptions)); + foreach ($bundleOptions as $option) { + $optionId = $option['option_id']; + $optionQty = $option['option_qty']; + $optionSelections = $option['option_selections']; + $this->assertArrayHasKey($optionId, $buyRequest['bundle_option']); + $this->assertEquals($optionQty, $buyRequest['bundle_option_qty'][$optionId]); + $this->assertEquals($optionSelections, $buyRequest['bundle_option'][$optionId]); + } + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php index 100ac28b9fb0b8af853c51d3b2d713973f7a735d..fc622bc2183a9ef34142e3d7a369626506f909d4 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php @@ -75,34 +75,26 @@ class CategoryRepositoryTest extends WebapiAbstract ]; return $this->_webApiCall($serviceInfo, ['categoryId' => $id]); } - /** - * @return array - */ - public function categoryCreationProvider() - { - return [ - [ - $this->getSimpleCategoryData( - [ - 'name' => 'Test Category Name', - ] - ), - ] - ]; - } /** * Test for create category process * * @magentoApiDataFixture Magento/Catalog/Model/Category/_files/service_category_create.php - * @dataProvider categoryCreationProvider */ - public function testCreate($category) + public function testCreate() { - $category = $this->createCategory($category); - $this->assertGreaterThan(0, $category['id']); + $categoryData = $this->getSimpleCategoryData(['name' => 'Test Category Name']); + $result = $this->createCategory($categoryData); + $this->assertGreaterThan(0, $result['id']); + foreach (['name', 'parent_id', 'available_sort_by'] as $fieldName) { + $this->assertEquals( + $categoryData[$fieldName], + $result[$fieldName], + sprintf('"%s" field value is invalid', $fieldName) + ); + } // delete category to clean up auto-generated url rewrites - $this->deleteCategory($category['id']); + $this->deleteCategory($result['id']); } /** @@ -193,6 +185,7 @@ class CategoryRepositoryTest extends WebapiAbstract ? $categoryData['name'] : uniqid('Category-', true), 'is_active' => '1', 'include_in_menu' => "1", + 'available_sort_by' => ['position', 'name'], 'custom_attributes' => [ ['attribute_code' => 'url_key', 'value' => ''], ['attribute_code' => 'description', 'value' => 'Custom description'], diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php index d022864b2b9c7365f113b75fd40cd782cba140a6..fcd48029ba06285c866283d638d87bf46baf79c8 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php @@ -9,6 +9,9 @@ namespace Magento\Catalog\Api; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\TestFramework\Helper\Bootstrap; +/** + * Class ProductAttributeMediaGalleryManagementInterfaceTest + */ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestFramework\TestCase\WebapiAbstract { /** @@ -106,6 +109,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF { $requestData = [ 'id' => null, + 'media_type' => \Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter::MEDIA_TYPE_CODE, 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -138,6 +142,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF { $requestData = [ 'id' => null, + 'media_type' => \Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter::MEDIA_TYPE_CODE, 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -187,6 +192,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF 'position' => 10, 'types' => ['thumbnail'], 'disabled' => true, + 'media_type' => 'image', ], // Store ID is not provided so the default one must be used ]; @@ -225,6 +231,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF 'position' => 10, 'types' => ['thumbnail'], 'disabled' => true, + 'media_type' => 'image', ], 'store_id' => 1, ]; @@ -279,6 +286,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF { $requestData = [ 'id' => null, + 'media_type' => 'image', 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -303,6 +311,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF $encodedContent = 'not_a_base64_encoded_content'; $requestData = [ 'id' => null, + 'media_type' => 'image', 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -327,6 +336,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF $encodedContent = base64_encode('not_an_image'); $requestData = [ 'id' => null, + 'media_type' => 'image', 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -351,6 +361,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF $encodedContent = base64_encode(file_get_contents($this->testImagePath)); $requestData = [ 'id' => null, + 'media_type' => 'image', 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -375,6 +386,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF $requestData = [ 'id' => null, + 'media_type' => 'image', 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -398,6 +410,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF { $requestData = [ 'id' => null, + 'media_type' => 'image', 'label' => 'Image Text', 'position' => 1, 'types' => ['image'], @@ -423,6 +436,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF 'sku' => 'simple', 'entry' => [ 'id' => $this->getTargetGalleryEntryId(), + 'media_type' => 'image', 'label' => 'Updated Image Text', 'position' => 10, 'types' => ['thumbnail'], @@ -449,6 +463,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF 'sku' => 'wrong_product_sku', 'entry' => [ 'id' => 9999, + 'media_type' => 'image', 'label' => 'Updated Image Text', 'position' => 1, 'types' => ['thumbnail'], @@ -471,6 +486,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF 'sku' => 'simple', 'entry' => [ 'id' => 9999, + 'media_type' => 'image', 'label' => 'Updated Image Text', 'position' => 1, 'types' => ['thumbnail'], @@ -532,6 +548,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF $expected = [ 'label' => $image['label'], + 'media_type' => $image['media_type'], 'position' => $image['position'], 'disabled' => (bool)$image['disabled'], 'file' => $image['file'], @@ -551,7 +568,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF ]; $requestData = [ 'sku' => $productSku, - 'imageId' => $imageId, + 'entryId' => $imageId, ]; $data = $this->_webApiCall($serviceInfo, $requestData); $actual = (array)$data; @@ -559,6 +576,7 @@ class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestF $this->assertEquals($expected['position'], $actual['position']); $this->assertEquals($expected['file'], $actual['file']); $this->assertEquals($expected['types'], $actual['types']); + $this->assertEquals($expected['media_type'], $actual['media_type']); $this->assertEquals($expected['disabled'], (bool)$actual['disabled']); } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductGroupPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductGroupPriceManagementTest.php deleted file mode 100644 index 716a9ac564048a33fee8391647fc136da8f58d67..0000000000000000000000000000000000000000 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductGroupPriceManagementTest.php +++ /dev/null @@ -1,118 +0,0 @@ -<?php -/** - * - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Api; - -use Magento\TestFramework\TestCase\WebapiAbstract; - -class ProductGroupPriceManagementTest extends WebapiAbstract -{ - const SERVICE_NAME = 'catalogProductGroupPriceManagementV1'; - const SERVICE_VERSION = 'V1'; - const RESOURCE_PATH = '/V1/products/'; - - /** - * @magentoApiDataFixture Magento/Catalog/_files/product_group_prices.php - */ - public function testGetList() - { - $productSku = 'simple_with_group_price'; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . $productSku . '/group-prices', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'GetList', - ], - ]; - $groupPriceList = $this->_webApiCall($serviceInfo, ['sku' => $productSku]); - $this->assertCount(2, $groupPriceList); - $this->assertEquals(9, $groupPriceList[0]['value']); - $this->assertEquals(7, $groupPriceList[1]['value']); - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/product_group_prices.php - */ - public function testDelete() - { - $productSku = 'simple_with_group_price'; - $customerGroupId = \Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . $productSku . "/group-prices/" . $customerGroupId, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'Remove', - ], - ]; - $requestData = ['sku' => $productSku, 'customerGroupId' => $customerGroupId]; - $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php - */ - public function testAdd() - { - $productSku = 'simple'; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => '/V1/products/' . $productSku . '/group-prices/1/price/10', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'Add', - ], - ]; - $this->_webApiCall($serviceInfo, ['sku' => $productSku, 'customerGroupId' => 1, 'price' => 10]); - $objectManager = \Magento\TestFramework\ObjectManager::getInstance(); - /** @var \Magento\Catalog\Api\ProductGroupPriceManagementInterface $service */ - $service = $objectManager->get('Magento\Catalog\Api\ProductGroupPriceManagementInterface'); - $prices = $service->getList($productSku); - $this->assertCount(1, $prices); - $this->assertEquals(10, $prices[0]->getValue()); - $this->assertEquals(1, $prices[0]->getCustomerGroupId()); - } - - /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php - * @magentoApiDataFixture Magento/Store/_files/website.php - */ - public function testAddForDifferentWebsite() - { - $productSku = 'simple'; - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => '/V1/products/' . $productSku . '/group-prices/1/price/10', - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'Add', - ], - - ]; - $this->_webApiCall($serviceInfo, ['sku' => $productSku, 'customerGroupId' => 1, 'price' => 10]); - $objectManager = \Magento\TestFramework\ObjectManager::getInstance(); - /** @var \Magento\Catalog\Api\ProductGroupPriceManagementInterface $service */ - $service = $objectManager->get('Magento\Catalog\Api\ProductGroupPriceManagementInterface'); - $prices = $service->getList($productSku); - $this->assertCount(1, $prices); - $this->assertEquals(10, $prices[0]->getValue()); - $this->assertEquals(1, $prices[0]->getCustomerGroupId()); - } -} diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php index 3aa38f8cdb53f505a5f2b7b530b176c47fac6a69..ff7e2abfbe54f57d705ad65da425bab15b5521ff 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php @@ -9,6 +9,9 @@ namespace Magento\Catalog\Api; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +/** + * @magentoAppIsolation enabled + */ class ProductLinkManagementInterfaceTest extends WebapiAbstract { const SERVICE_NAME = 'catalogProductLinkManagementV1'; @@ -85,7 +88,7 @@ class ProductLinkManagementInterfaceTest extends WebapiAbstract /** * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php - * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual_in_stock.php */ public function testAssign() { diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index d33e1456bc363e80830d3d1a32229fbccaaebc69..9389a8de506f0088eeea6e1a5f32a9bd501c4e3d 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -6,16 +6,19 @@ namespace Magento\Catalog\Api; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\Framework\Webapi\Exception as HTTPExceptionCodes; +/** + * @magentoAppIsolation enabled + */ class ProductRepositoryInterfaceTest extends WebapiAbstract { const SERVICE_NAME = 'catalogProductRepositoryV1'; const SERVICE_VERSION = 'V1'; const RESOURCE_PATH = '/V1/products'; - const KEY_GROUP_PRICES = 'group_prices'; const KEY_TIER_PRICES = 'tier_prices'; /** @@ -139,6 +142,9 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract ProductInterface::STATUS => 1, ProductInterface::TYPE_ID => 'simple', ProductInterface::ATTRIBUTE_SET_ID => 4, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [ + 'stock_item' => $this->getStockItemData() + ] ]; $this->saveProduct($productData); @@ -323,6 +329,7 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $productData['media_gallery_entries'] = [ [ 'position' => 1, + 'media_type' => 'image', 'disabled' => true, 'label' => 'tiny1', 'types' => [], @@ -334,6 +341,7 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract ], [ 'position' => 2, + 'media_type' => 'image', 'disabled' => false, 'label' => 'tiny2', 'types' => ['image', 'small_image'], @@ -356,6 +364,7 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract [ 'label' => 'tiny1', 'position' => 1, + 'media_type' => 'image', 'disabled' => true, 'types' => [], 'file' => '/t/i/' . $filename1, @@ -363,6 +372,7 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract [ 'label' => 'tiny2', 'position' => 2, + 'media_type' => 'image', 'disabled' => false, 'types' => ['image', 'small_image'], 'file' => '/t/i/' . $filename2, @@ -373,6 +383,7 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $response['media_gallery_entries'] = [ [ 'id' => $id, + 'media_type' => 'image', 'label' => 'tiny1_new_label', 'position' => 1, 'disabled' => false, @@ -384,15 +395,14 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $mediaGalleryEntries = $response['media_gallery_entries']; $this->assertEquals(1, count($mediaGalleryEntries)); unset($mediaGalleryEntries[0]['id']); - $expectedValue = [ - [ - 'label' => 'tiny1_new_label', - 'position' => 1, - 'disabled' => false, - 'types' => ['image', 'small_image'], - 'file' => '/t/i/' . $filename1, - ] - ]; + $expectedValue = [[ + 'label' => 'tiny1_new_label', + 'media_type' => 'image', + 'position' => 1, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'file' => '/t/i/' . $filename1, + ]]; $this->assertEquals($expectedValue, $mediaGalleryEntries); //don't set the media_gallery_entries field, existing entry should not be touched unset($response['media_gallery_entries']); @@ -623,79 +633,6 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $this->_webApiCall($serviceInfo, ['sku' => $sku]) : $this->_webApiCall($serviceInfo); } - public function testGroupPrices() - { - // create a product with group prices - $custGroup1 = \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID; - $custGroup2 = \Magento\Customer\Model\Group::CUST_GROUP_ALL; - $productData = $this->getSimpleProductData(); - $productData[self::KEY_GROUP_PRICES] = [ - [ - 'customer_group_id' => $custGroup1, - 'value' => 3.14 - ], - [ - 'customer_group_id' => $custGroup2, - 'value' => 3.45, - ] - ]; - $this->saveProduct($productData); - $response = $this->getProduct($productData[ProductInterface::SKU]); - - $this->assertArrayHasKey(self::KEY_GROUP_PRICES, $response); - $groupPrices = $response[self::KEY_GROUP_PRICES]; - $this->assertNotNull($groupPrices, "CREATE: expected to have group prices"); - $this->assertCount(2, $groupPrices, "CREATE: expected to have 2 'group_prices' objects"); - $this->assertEquals(3.14, $groupPrices[0]['value']); - $this->assertEquals($custGroup1, $groupPrices[0]['customer_group_id']); - $this->assertEquals(3.45, $groupPrices[1]['value']); - $this->assertEquals($custGroup2, $groupPrices[1]['customer_group_id']); - - // update the product's group prices: update 1st group price, (delete the 2nd group price), add a new one - $custGroup3 = 1; - $groupPrices[0]['value'] = 3.33; - $groupPrices[1] = [ - 'customer_group_id' => $custGroup3, - 'value' => 2.10, - ]; - $response[self::KEY_GROUP_PRICES] = $groupPrices; - $response = $this->updateProduct($response); - - $this->assertArrayHasKey(self::KEY_GROUP_PRICES, $response); - $groupPrices = $response[self::KEY_GROUP_PRICES]; - $this->assertNotNull($groupPrices, "UPDATE 1: expected to have group prices"); - $this->assertCount(2, $groupPrices, "UPDATE 1: expected to have 2 'group_prices' objects"); - $this->assertEquals(3.33, $groupPrices[0]['value']); - $this->assertEquals($custGroup1, $groupPrices[0]['customer_group_id']); - $this->assertEquals(2.10, $groupPrices[1]['value']); - $this->assertEquals($custGroup3, $groupPrices[1]['customer_group_id']); - - // update the product without any mention of group prices; no change expected for group pricing - $response = $this->getProduct($productData[ProductInterface::SKU]); - unset($response[self::KEY_GROUP_PRICES]); - $response = $this->updateProduct($response); - - $this->assertArrayHasKey(self::KEY_GROUP_PRICES, $response); - $groupPrices = $response[self::KEY_GROUP_PRICES]; - $this->assertNotNull($groupPrices, "UPDATE 2: expected to have group prices"); - $this->assertCount(2, $groupPrices, "UPDATE 2: expected to have 2 'group_prices' objects"); - $this->assertEquals(3.33, $groupPrices[0]['value']); - $this->assertEquals($custGroup1, $groupPrices[0]['customer_group_id']); - $this->assertEquals(2.10, $groupPrices[1]['value']); - $this->assertEquals($custGroup3, $groupPrices[1]['customer_group_id']); - - // update the product with empty group prices; expect to have the existing group prices removed - $response = $this->getProduct($productData[ProductInterface::SKU]); - $response[self::KEY_GROUP_PRICES] = []; - $response = $this->updateProduct($response); - $this->assertArrayHasKey(self::KEY_GROUP_PRICES, $response, "expected to have the 'group_prices' key"); - $this->assertEmpty($response[self::KEY_GROUP_PRICES], "expected to have an empty array of 'group_prices'"); - - // delete the product with group prices; expect that all goes well - $response = $this->deleteProduct($productData[ProductInterface::SKU]); - $this->assertTrue($response); - } - public function testTierPrices() { // create a product with tier prices @@ -778,4 +715,36 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $response = $this->deleteProduct($productData[ProductInterface::SKU]); $this->assertTrue($response); } + + /** + * @return array + */ + private function getStockItemData() + { + return [ + StockItemInterface::IS_IN_STOCK => 1, + StockItemInterface::QTY => 100500, + StockItemInterface::IS_QTY_DECIMAL => 1, + StockItemInterface::SHOW_DEFAULT_NOTIFICATION_MESSAGE => 0, + StockItemInterface::USE_CONFIG_MIN_QTY => 0, + StockItemInterface::USE_CONFIG_MIN_SALE_QTY => 0, + StockItemInterface::MIN_QTY => 1, + StockItemInterface::MIN_SALE_QTY => 1, + StockItemInterface::MAX_SALE_QTY => 100, + StockItemInterface::USE_CONFIG_MAX_SALE_QTY => 0, + StockItemInterface::USE_CONFIG_BACKORDERS => 0, + StockItemInterface::BACKORDERS => 0, + StockItemInterface::USE_CONFIG_NOTIFY_STOCK_QTY => 0, + StockItemInterface::NOTIFY_STOCK_QTY => 0, + StockItemInterface::USE_CONFIG_QTY_INCREMENTS => 0, + StockItemInterface::QTY_INCREMENTS => 0, + StockItemInterface::USE_CONFIG_ENABLE_QTY_INC => 0, + StockItemInterface::ENABLE_QTY_INCREMENTS => 0, + StockItemInterface::USE_CONFIG_MANAGE_STOCK => 1, + StockItemInterface::MANAGE_STOCK => 1, + StockItemInterface::LOW_STOCK_DATE => null, + StockItemInterface::IS_DECIMAL_DIVIDED => 0, + StockItemInterface::STOCK_STATUS_CHANGED_AUTO => 0, + ]; + } } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php index 31e686b2e13dbd0edb704c85d4e772c9c7802cac..fd5d27d7a7689931eaec5655e2069426567a437f 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php @@ -36,15 +36,15 @@ class ProductTierPriceManagementTest extends WebapiAbstract ], ]; - $groupPriceList = $this->_webApiCall( + $tearPriceList = $this->_webApiCall( $serviceInfo, ['sku' => $productSku, 'customerGroupId' => $customerGroupId] ); - $this->assertCount($count, $groupPriceList); + $this->assertCount($count, $tearPriceList); if ($count) { - $this->assertEquals($value, $groupPriceList[0]['value']); - $this->assertEquals($qty, $groupPriceList[0]['qty']); + $this->assertEquals($value, $tearPriceList[0]['value']); + $this->assertEquals($qty, $tearPriceList[0]['qty']); } } diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php index 0f49b8bef0ae3f56dfff91db4c9ae8cc25f371c4..606c42e6b1269fa16cd5fc1c279fb7f561232425 100644 --- a/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php +++ b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php @@ -90,7 +90,7 @@ class StockItemTest extends WebapiAbstract $arguments = ['productSku' => $productSku]; $apiResult = $this->_webApiCall($serviceInfo, $arguments); $result['item_id'] = $apiResult['item_id']; - $this->assertEquals($result, $apiResult, 'The stock data does not match.'); + $this->assertEquals($result, array_intersect_key($apiResult, $result), 'The stock data does not match.'); return $apiResult; } @@ -140,7 +140,7 @@ class StockItemTest extends WebapiAbstract $stockItemResource = $this->objectManager->get('Magento\CatalogInventory\Model\Resource\Stock\Item'); $stockItemResource->loadByProductId($stockItem, $stockItemOld['product_id'], $stockItemOld['website_id']); $expectedResult['item_id'] = $stockItem->getItemId(); - $this->assertEquals($expectedResult, $stockItem->getData()); + $this->assertEquals($expectedResult, array_intersect_key($stockItem->getData(), $expectedResult)); } /** @@ -234,7 +234,7 @@ class StockItemTest extends WebapiAbstract 'manage_stock' => 1, 'low_stock_date' => '', 'is_decimal_divided' => '', - 'stock_status_changed_auto' => 0 + 'stock_status_changed_auto' => 0, ], ], ]; diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/CartItemRepositoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cf6ae967dfbec92f8984b186702c1da86b4121bd --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/CartItemRepositoryTest.php @@ -0,0 +1,361 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Api; + +use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +class CartItemRepositoryTest extends WebapiAbstract +{ + const SERVICE_NAME = 'quoteCartItemRepositoryV1'; + const SERVICE_VERSION = 'V1'; + const RESOURCE_PATH = '/V1/carts/items'; + const CONFIGURABLE_PRODUCT_SKU = 'configurable'; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testAddProduct() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('test_order_1', 'reserved_order_id'); + $cartId = $quote->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $response = $this->_webApiCall($serviceInfo, $this->getRequestData($cartId)); + $this->assertNotNull($response['item_id']); + $this->assertEquals(Configurable::TYPE_CODE, $response['product_type']); + + $quote->load('test_order_1', 'reserved_order_id'); + $items = $quote->getAllItems(); + $this->assertGreaterThan(0, count($items)); + + /** @var \Magento\Quote\Model\Resource\Quote\Item|null $item */ + $item = null; + /** @var \Magento\Quote\Model\Resource\Quote\Item $quoteItem */ + foreach ($items as $quoteItem) { + if ($quoteItem->getProductType() == Configurable::TYPE_CODE && !$quoteItem->getParentItemId()) { + $item = $quoteItem; + break; + } + } + $this->assertNotNull($item); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @expectedException \Exception + * @expectedExceptionMessage You need to choose options for your item. + */ + public function testAddProductWithIncorrectOptions() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('test_order_1', 'reserved_order_id'); + $cartId = $quote->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $requestData = $this->getRequestData($cartId); + $requestData['cartItem']['product_option']['extension_attributes'] + ['configurable_item_options'][0]['option_id'] = 1000; + + $requestData['cartItem']['product_option']['extension_attributes'] + ['configurable_item_options'][0]['option_value'] = 2000; + + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + * @expectedException \Exception + * @expectedExceptionMessage Cart %1 doesn't contain item %2 + */ + public function testUpdateIncorrectItem() + { + $qty = 1; + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('test_cart_with_configurable', 'reserved_order_id'); + $cartId = $quote->getId(); + + $requestData = $this->getRequestData($cartId, 1); + $requestData['cartItem']['qty'] = $qty; + $requestData['cartItem']['item_id'] = 1000; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/1000', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + */ + public function testUpdate() + { + $qty = 4; + $this->updateStockForItem(10, 100); + $this->updateStockForItem(20, 100); + + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('test_cart_with_configurable', 'reserved_order_id'); + $cartId = $quote->getId(); + + $items = $quote->getAllItems(); + $this->assertGreaterThan(0, count($items)); + + /** @var \Magento\Quote\Model\Resource\Quote\Item|null $item */ + $item = null; + /** @var \Magento\Quote\Model\Resource\Quote\Item $quoteItem */ + foreach ($items as $quoteItem) { + if ($quoteItem->getProductType() == Configurable::TYPE_CODE) { + $item = $quoteItem; + break; + } + } + + $this->assertNotNull($item); + $this->assertNotNull($item->getId()); + $this->assertEquals(Configurable::TYPE_CODE, $item->getProductType()); + + $requestData = $this->getRequestData($cartId, 1); + $requestData['cartItem']['qty'] = $qty; + $requestData['cartItem']['item_id'] = $item->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $item->getId(), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + $response = $this->_webApiCall($serviceInfo, $requestData); + + $this->assertNotNull($response['item_id']); + $this->assertEquals(Configurable::TYPE_CODE, $response['product_type']); + $this->assertEquals($cartId, $response['quote_id']); + $this->assertEquals($qty, $response['qty']); + $this->assertEquals( + $response['product_option']['extension_attributes']['configurable_item_options'][0], + $requestData['cartItem']['product_option']['extension_attributes']['configurable_item_options'][0] + ); + } + + /** + * @param int $itemId + * @param int $qty + */ + protected function updateStockForItem($itemId, $qty) + { + /** @var \Magento\CatalogInventory\Model\Stock\Status $stockStatus */ + $stockStatus = $this->objectManager->create('Magento\CatalogInventory\Model\Stock\Status'); + $stockStatus->load($itemId, 'product_id'); + if (!$stockStatus->getProductId()) { + $stockStatus->setProductId($itemId); + } + $stockStatus->setQty($qty); + $stockStatus->setStockStatus(1); + $stockStatus->save(); + + /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ + $stockItem = $this->objectManager->create('Magento\CatalogInventory\Model\Stock\Item'); + $stockItem->load($itemId, 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($itemId); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty($qty); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock(1); + $stockItem->save(); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + */ + public function testUpdateQty() + { + $qty = 1; + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('test_cart_with_configurable', 'reserved_order_id'); + $cartId = $quote->getId(); + + $items = $quote->getAllItems(); + $this->assertGreaterThan(0, count($items)); + + /** @var \Magento\Quote\Model\Resource\Quote\Item|null $item */ + $item = null; + /** @var \Magento\Quote\Model\Resource\Quote\Item $quoteItem */ + foreach ($items as $quoteItem) { + if ($quoteItem->getProductType() == Configurable::TYPE_CODE) { + $item = $quoteItem; + break; + } + } + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $item->getId(), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $this->assertNotNull($item); + $this->assertNotNull($item->getId()); + $this->assertEquals(Configurable::TYPE_CODE, $item->getProductType()); + + $requestData = $this->getRequestData($cartId); + $requestData['cartItem']['qty'] = $qty; + $requestData['cartItem']['item_id'] = $item->getId(); + $requestData['cartItem']['product_option'] = null; + + $response = $this->_webApiCall($serviceInfo, $requestData); + + $this->assertNotNull($response['item_id']); + $this->assertEquals($item->getId(), $response['item_id']); + $this->assertEquals(Configurable::TYPE_CODE, $response['product_type']); + $this->assertEquals($cartId, $response['quote_id']); + $this->assertEquals($qty, $response['qty']); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + */ + public function testGetList() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('test_cart_with_configurable', 'reserved_order_id'); + $cartId = $quote->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/carts/' . $cartId . '/items', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'GetList', + ], + ]; + $response = $this->_webApiCall($serviceInfo, ['cartId' => $cartId]); + + $this->assertGreaterThan(0, count($response)); + $item = $response[0]; + + $this->assertNotNull($item['item_id']); + $this->assertEquals(Configurable::TYPE_CODE, $item['product_type']); + $this->assertArrayHasKey('product_option', $item); + $this->assertArrayHasKey('extension_attributes', $item['product_option']); + $this->assertArrayHasKey('configurable_item_options', $item['product_option']['extension_attributes']); + + $options = $item['product_option']['extension_attributes']['configurable_item_options']; + $this->assertGreaterThan(0, count($options)); + + $this->assertArrayHasKey('option_id', $options[0]); + $this->assertArrayHasKey('option_value', $options[0]); + + $this->assertNotNull($options[0]['option_id']); + $this->assertNotNull($options[0]['option_value']); + } + + /** + * @param $cartId + * @param null $selectedOption + * @return array + */ + protected function getRequestData($cartId, $selectedOption = null) + { + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->create('Magento\Catalog\Api\ProductRepositoryInterface'); + $product = $productRepository->get(self::CONFIGURABLE_PRODUCT_SKU); + + $configurableProductOptions = $product->getExtensionAttributes()->getConfigurableProductOptions(); + + $optionKey = 0; + if ($selectedOption && isset($options[$selectedOption])) { + $optionKey = $selectedOption; + } + + $attributeId = $configurableProductOptions[0]->getAttributeId(); + $options = $configurableProductOptions[0]->getOptions(); + $optionId = $options[$optionKey]['value_index']; + + return [ + 'cartItem' => [ + 'sku' => self::CONFIGURABLE_PRODUCT_SKU, + 'qty' => 1, + 'quote_id' => $cartId, + 'product_option' => [ + 'extension_attributes' => [ + 'configurable_item_options' => [ + [ + 'option_id' => $attributeId, + 'option_value' => $optionId + ] + ] + ] + ] + ] + ]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php index 4522abb56de221f63893998e61fc5a538b667a57..37b5113ea754078abe6dd3307e46761677574380 100644 --- a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php @@ -71,7 +71,6 @@ class ConfigurableProductManagementTest extends \Magento\TestFramework\TestCase\ 'value' => $attributeOptionValue ] ], - 'group_prices' => [], 'tier_prices' => [] ] ]; diff --git a/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/CartItemRepositoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9e3512653a71820ada050b1b8dabd1cc3dd5cad6 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Downloadable/Api/CartItemRepositoryTest.php @@ -0,0 +1,328 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Downloadable\Api; + +use Magento\TestFramework\TestCase\WebapiAbstract; + +class CartItemRepositoryTest extends WebapiAbstract +{ + const SERVICE_VERSION = 'V1'; + const SERVICE_NAME = 'quoteCartItemRepositoryV1'; + const RESOURCE_PATH = '/V1/carts/'; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Quote/_files/empty_quote.php + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php + */ + public function testAddItem() + { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create('Magento\Catalog\Model\Product')->load(1); + $productSku = $product->getSku(); + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('reserved_order_id', 'reserved_order_id'); + $cartId = $quote->getId(); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + // use ID of the first downloadable link + $linkId = array_values($product->getDownloadableLinks())[0]->getId(); + + $requestData = [ + 'cartItem' => [ + 'sku' => $productSku, + 'qty' => 1, + 'quote_id' => $cartId, + 'product_option' => [ + 'extension_attributes' => [ + 'downloadable_option' => [ + 'downloadable_links' => [$linkId] + ] + ] + ] + ], + ]; + $response = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($response); + $this->assertEquals('downloadable-product', $response['sku']); + $this->assertEquals(1, $response['qty']); + $this->assertCount( + 1, + $response['product_option']['extension_attributes']['downloadable_option']['downloadable_links'] + ); + $this->assertContains( + $linkId, + $response['product_option']['extension_attributes']['downloadable_option']['downloadable_links'] + ); + } + + /** + * @magentoApiDataFixture Magento/Quote/_files/empty_quote.php + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php + * @expectedException \Exception + */ + public function testAddItemWithInvalidLinkId() + { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create('Magento\Catalog\Model\Product')->load(1); + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('reserved_order_id', 'reserved_order_id'); + $cartId = $quote->getId(); + $productSku = $product->getSku(); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $linkId = 9999; + + $requestData = [ + 'cartItem' => [ + 'sku' => $productSku, + 'qty' => 1, + 'quote_id' => $cartId, + 'product_option' => [ + 'extension_attributes' => [ + 'downloadable_option' => [ + 'downloadable_links' => [$linkId] + ] + ] + ] + ], + ]; + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * @magentoApiDataFixture Magento/Downloadable/_files/quote_with_downloadable_product.php + */ + public function testUpdateItem() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('reserved_order_id_1', 'reserved_order_id'); + $cartId = $quote->getId(); + $product = $this->objectManager->create('Magento\Catalog\Model\Product'); + $product->load($product->getIdBySku('downloadable-product')); + // use ID of the first quote item + $itemId = $quote->getAllItems()[0]->getId(); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items/' . $itemId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + // use ID of the first downloadable link + $linkId = array_values($product->getDownloadableLinks())[0]->getId(); + + $requestData = [ + 'cartItem' => [ + 'qty' => 2, + 'quote_id' => $cartId, + 'item_id' => $itemId, + 'sku' => 'downloadable-product', + 'product_option' => [ + 'extension_attributes' => [ + 'downloadable_option' => [ + 'downloadable_links' => [$linkId] + ] + ] + ] + ], + ]; + $response = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($response); + $this->assertEquals('downloadable-product', $response['sku']); + $this->assertEquals(2, $response['qty']); + $this->assertCount( + 1, + $response['product_option']['extension_attributes']['downloadable_option']['downloadable_links'] + ); + $this->assertContains( + $linkId, + $response['product_option']['extension_attributes']['downloadable_option']['downloadable_links'] + ); + } + + /** + * @magentoApiDataFixture Magento/Downloadable/_files/quote_with_downloadable_product.php + * @expectedException \Exception + */ + public function testUpdateItemWithInvalidLinkId() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('reserved_order_id_1', 'reserved_order_id'); + $cartId = $quote->getId(); + $product = $this->objectManager->create('Magento\Catalog\Model\Product'); + $product->load($product->getIdBySku('downloadable-product')); + // use ID of the first quote item + $itemId = $quote->getAllItems()[0]->getId(); + $serviceInfo = [ + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items/' . $itemId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + ]; + + $linkId = 9999; + + $requestData = [ + 'cartItem' => [ + 'qty' => 2, + 'quote_id' => $cartId, + 'item_id' => $itemId, + 'sku' => 'downloadable-product', + 'product_option' => [ + 'extension_attributes' => [ + 'downloadable_option' => [ + 'downloadable_links' => [$linkId] + ] + ] + ] + ], + ]; + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * @magentoApiDataFixture Magento/Downloadable/_files/quote_with_downloadable_product.php + */ + public function testGetList() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('reserved_order_id_1', 'reserved_order_id'); + $cartId = $quote->getId(); + $product = $this->objectManager->create('Magento\Catalog\Model\Product'); + $product->load($product->getIdBySku('downloadable-product')); + // use ID of the first downloadable link + $linkId = array_values($product->getDownloadableLinks())[0]->getId(); + + /** @var \Magento\Quote\Api\Data\CartItemInterface $item */ + $item = $quote->getAllItems()[0]; + $expectedResult = [[ + 'item_id' => $item->getItemId(), + 'sku' => $item->getSku(), + 'name' => $item->getName(), + 'price' => $item->getPrice(), + 'qty' => $item->getQty(), + 'product_type' => $item->getProductType(), + 'quote_id' => $item->getQuoteId(), + 'product_option' => [ + 'extension_attributes' => [ + 'downloadable_option' => [ + 'downloadable_links' => [$linkId] + ] + ] + ] + ]]; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $cartId . '/items', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'GetList', + ], + ]; + + $requestData = ["cartId" => $cartId]; + $this->assertEquals($expectedResult, $this->_webApiCall($serviceInfo, $requestData)); + } + + /** + * @magentoApiDataFixture Magento/Downloadable/_files/quote_with_downloadable_product.php + */ + public function testUpdateItemQty() + { + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $this->objectManager->create('Magento\Quote\Model\Quote'); + $quote->load('reserved_order_id_1', 'reserved_order_id'); + $product = $this->objectManager->create('Magento\Catalog\Model\Product'); + $product->load($product->getIdBySku('downloadable-product')); + $cartId = $quote->getId(); + // use ID of the first quote item + $itemId = $quote->getAllItems()[0]->getId(); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'items/' . $itemId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + // use ID of the first downloadable link + $linkId = array_values($product->getDownloadableLinks())[0]->getId(); + + $requestData = [ + 'cartItem' => [ + 'qty' => 2, + 'quote_id' => $cartId, + 'item_id' => $itemId, + 'sku' => 'downloadable-product', + ], + ]; + $response = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($response); + $this->assertEquals('downloadable-product', $response['sku']); + $this->assertEquals(2, $response['qty']); + $this->assertCount( + 1, + $response['product_option']['extension_attributes']['downloadable_option']['downloadable_links'] + ); + $this->assertContains( + $linkId, + $response['product_option']['extension_attributes']['downloadable_option']['downloadable_links'] + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php index 88160057b07004339713b92e36d1fc93be910cae..5522d62bd0259d260e6d7132f641bfedda6cbeca 100644 --- a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php @@ -6,6 +6,9 @@ */ namespace Magento\GroupedProduct\Api; +/** + * @magentoAppIsolation enabled + */ class ProductLinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract { const SERVICE_NAME = 'catalogProductLinkManagementV1'; diff --git a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductRepositoryInterfaceTest.php index 8adffb82f70547d69249fde8e57dcfcce1643a13..f4a8bab17e58d6f475677c6bdb70c062b478502d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductRepositoryInterfaceTest.php @@ -6,6 +6,7 @@ namespace Magento\GroupedProduct\Api; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\TestFramework\TestCase\WebapiAbstract; class ProductRepositoryInterfaceTest extends WebapiAbstract @@ -126,6 +127,9 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract ProductInterface::STATUS => 1, ProductInterface::TYPE_ID => 'simple', ProductInterface::ATTRIBUTE_SET_ID => 4, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [ + 'stock_item' => $this->getStockItemData() + ] ]; $this->saveProduct($productData); @@ -200,4 +204,36 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $this->deleteProduct("product_simple_500"); $this->deleteProduct("group_product_500"); } + + /** + * @return array + */ + private function getStockItemData() + { + return [ + StockItemInterface::IS_IN_STOCK => 1, + StockItemInterface::QTY => 100500, + StockItemInterface::IS_QTY_DECIMAL => 1, + StockItemInterface::SHOW_DEFAULT_NOTIFICATION_MESSAGE => 0, + StockItemInterface::USE_CONFIG_MIN_QTY => 0, + StockItemInterface::USE_CONFIG_MIN_SALE_QTY => 0, + StockItemInterface::MIN_QTY => 1, + StockItemInterface::MIN_SALE_QTY => 1, + StockItemInterface::MAX_SALE_QTY => 100, + StockItemInterface::USE_CONFIG_MAX_SALE_QTY => 0, + StockItemInterface::USE_CONFIG_BACKORDERS => 0, + StockItemInterface::BACKORDERS => 0, + StockItemInterface::USE_CONFIG_NOTIFY_STOCK_QTY => 0, + StockItemInterface::NOTIFY_STOCK_QTY => 0, + StockItemInterface::USE_CONFIG_QTY_INCREMENTS => 0, + StockItemInterface::QTY_INCREMENTS => 0, + StockItemInterface::USE_CONFIG_ENABLE_QTY_INC => 0, + StockItemInterface::ENABLE_QTY_INCREMENTS => 0, + StockItemInterface::USE_CONFIG_MANAGE_STOCK => 1, + StockItemInterface::MANAGE_STOCK => 1, + StockItemInterface::LOW_STOCK_DATE => null, + StockItemInterface::IS_DECIMAL_DIVIDED => 0, + StockItemInterface::STOCK_STATUS_CHANGED_AUTO => 0, + ]; + } } diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestPaymentMethodManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestPaymentMethodManagementTest.php index 5eed70a7f141a7282705421a2248c0e132fb04c7..a1e2789c99142741e413079d6ecbf3be90550c7b 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestPaymentMethodManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestPaymentMethodManagementTest.php @@ -81,7 +81,6 @@ class GuestPaymentMethodManagementTest extends \Magento\TestFramework\TestCase\W "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => null, 'cc_owner' => 'John', 'cc_type' => null, @@ -119,7 +118,6 @@ class GuestPaymentMethodManagementTest extends \Magento\TestFramework\TestCase\W "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', @@ -156,7 +154,6 @@ class GuestPaymentMethodManagementTest extends \Magento\TestFramework\TestCase\W "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', @@ -196,7 +193,6 @@ class GuestPaymentMethodManagementTest extends \Magento\TestFramework\TestCase\W "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/PaymentMethodManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/PaymentMethodManagementTest.php index 059ef3b14792e15f6474c029645ba3c486b8ff62..8a6115ef70be0b80a8e42b1ee9df067fafdbb544 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/PaymentMethodManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/PaymentMethodManagementTest.php @@ -47,7 +47,6 @@ class PaymentMethodManagementTest extends \Magento\TestFramework\TestCase\Webapi "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => null, 'cc_owner' => 'John', 'cc_type' => null, @@ -85,7 +84,6 @@ class PaymentMethodManagementTest extends \Magento\TestFramework\TestCase\Webapi "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', @@ -122,7 +120,6 @@ class PaymentMethodManagementTest extends \Magento\TestFramework\TestCase\Webapi "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', @@ -162,7 +159,6 @@ class PaymentMethodManagementTest extends \Magento\TestFramework\TestCase\Webapi "cartId" => $cartId, "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', @@ -321,7 +317,6 @@ class PaymentMethodManagementTest extends \Magento\TestFramework\TestCase\Webapi $requestData = [ "method" => [ 'method' => 'checkmo', - 'title' => 'title', 'po_number' => '200', 'cc_owner' => 'tester', 'cc_type' => 'test', diff --git a/dev/tests/functional/composer.json b/dev/tests/functional/composer.json index 1287f8a2f00f08d893cd3171baa9cb44eefa72bf..5f941b700f5402a510723d4e33ae4bfb570b8467 100644 --- a/dev/tests/functional/composer.json +++ b/dev/tests/functional/composer.json @@ -1,6 +1,6 @@ { "require": { - "magento/mtf": "1.0.0-rc32", + "magento/mtf": "1.0.0-rc35", "php": "~5.5.0|~5.6.0|~7.0.0", "phpunit/phpunit": "4.1.0", "phpunit/phpunit-selenium": ">=1.2" diff --git a/dev/tests/functional/etc/events.xml b/dev/tests/functional/etc/events.xml index 76fddfc79e67510e30f9d1b773dd16bf52214948..e838968e1c2a20a92b0431055088cf24f249aded 100644 --- a/dev/tests/functional/etc/events.xml +++ b/dev/tests/functional/etc/events.xml @@ -6,4 +6,9 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="events.xsd"> + <preset name="base"> + <observer class="Magento\Mtf\System\Observer\WebapiResponse"> + <tag name="webapi_failed"/> + </observer> + </preset> </config> diff --git a/dev/tests/functional/lib/Magento/Mtf/System/Observer/WebapiResponse.php b/dev/tests/functional/lib/Magento/Mtf/System/Observer/WebapiResponse.php new file mode 100644 index 0000000000000000000000000000000000000000..2956d7c0269bde89c4b25d89734bbd535497ec7d --- /dev/null +++ b/dev/tests/functional/lib/Magento/Mtf/System/Observer/WebapiResponse.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Mtf\System\Observer; + +use Magento\Mtf\System\Event\Event; + +/** + * Observer for obtaining response of web api handler. + */ +class WebapiResponse extends AbstractObserver +{ + /** + * File name of response source. + */ + const FILE_NAME = 'webapi_response.log'; + + /** + * Collect response source artifact to storage. + * + * @param Event $event + * @return void + */ + public function process(Event $event) + { + $directory = $this->createDestinationDirectory('webapi-response'); + $this->logger->log(json_encode($event->getSubjects()[0]), $directory . '/' . $event->getIdentifier() . '.json'); + } +} diff --git a/dev/tests/functional/phpunit.xml.dist b/dev/tests/functional/phpunit.xml.dist index ec1748e20b13556da45e875c29870dcdd70f72b1..f5f293ffa39f5099974a754655166834d26eb9a2 100644 --- a/dev/tests/functional/phpunit.xml.dist +++ b/dev/tests/functional/phpunit.xml.dist @@ -38,6 +38,7 @@ <env name="module_whitelist" value="Magento_Install" /> <env name="basedir" value="var/log" /> <env name="credentials_file_path" value="./credentials.xml.dist" /> + <env name="mage_mode" value="developer" /> </php> </phpunit> 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 1ccc6bde71ecff23979d44f995e55f7300d3680d..74541d4f5662e088e0aee224a14701b6ed7c4035 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 @@ -7,7 +7,7 @@ --> <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"> - <variation name="OnePageCheckoutTestVariation17" summary="Check Out as a Guest with Authorize.Net and Offline Shipping method" ticketId="MAGETWO-12832"> + <variation name="OnePageCheckoutAuthorizenetTestVariation1" summary="Check Out as a Guest with Authorize.Net and Offline Shipping method" ticketId="MAGETWO-12832"> <data name="products" xsi:type="string">catalogProductSimple::product_10_dollar, configurableProduct::with_one_option, bundleProduct::bundle_fixed_100_dollar_product</data> <data name="taxRule" xsi:type="string">us_ca_ny_rule</data> <data name="customer/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Cache/Additional.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Cache/Additional.php index 30f7194a98fe6ee74c2949c5adad9df2bf472160..291174b60bbd43f125ac243fba8b5e3a30c74518 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Cache/Additional.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Cache/Additional.php @@ -19,7 +19,7 @@ class Additional extends Block * * @var string */ - protected $flushButton = './/div/button[normalize-space(.)= "%s"]'; + protected $flushButton = './/button[normalize-space(.)= "%s"]'; /** * Flush cache in 'Additional Cache Management'. @@ -31,4 +31,16 @@ class Additional extends Block { $this->_rootElement->find(sprintf($this->flushButton, $flushButtonName), Locator::SELECTOR_XPATH)->click(); } + + /** + * Check if button is visible in 'Additional Cache Management'. + * + * @param string $flushButtonName + * @return bool + */ + public function isFlushCacheButtonVisible($flushButtonName) + { + return $this->_rootElement->find(sprintf($this->flushButton, $flushButtonName), Locator::SELECTOR_XPATH) + ->isVisible(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheFlushStaticFilesInProductionMode.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheFlushStaticFilesInProductionMode.php new file mode 100644 index 0000000000000000000000000000000000000000..dce65ed1bc54ab6d74b46962809f9a40c85e7681 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheFlushStaticFilesInProductionMode.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Backend\Test\Constraint; + +use Magento\Backend\Test\Page\Adminhtml\AdminCache; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert Flush Static Files Cache button not visible in production mode. + */ +class AssertCacheFlushStaticFilesInProductionMode extends AbstractConstraint +{ + const FLUSH_STATIC_FILES_CACHE = 'Flush Static Files Cache'; + + /** + * Assert Flush Static Files Cache button not visible in production mode. + * + * @param AdminCache $adminCache + * @return void + */ + public function processAssert(AdminCache $adminCache) + { + \PHPUnit_Framework_Assert::assertFalse( + $adminCache->getAdditionalBlock()->isFlushCacheButtonVisible(self::FLUSH_STATIC_FILES_CACHE), + self::FLUSH_STATIC_FILES_CACHE . ' button should not be visible in production mode.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return self::FLUSH_STATIC_FILES_CACHE . ' button is not visible in production mode.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheManagementAction.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheManagementAction.php index 30e7a986ba8e8f361f0d7d75517053777a5156e5..5ca1e4e677bbf7f3fa1ecf19f25114a19e547a2e 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheManagementAction.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertCacheManagementAction.php @@ -6,7 +6,6 @@ namespace Magento\Backend\Test\Constraint; -use Magento\Backend\Test\Fixture\GlobalSearch; use Magento\Backend\Test\Page\Adminhtml\AdminCache; use Magento\Mtf\Constraint\AbstractConstraint; @@ -16,7 +15,7 @@ use Magento\Mtf\Constraint\AbstractConstraint; class AssertCacheManagementAction extends AbstractConstraint { /** - * Assert that backend page has correct title and 404 Error is absent on the page. + * Assert success message for cache flush. * * @param AdminCache $adminCache * @param string $successMessage diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/AdminCache.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/AdminCache.xml index ecfd99d31c1e93bfb1533143b41672d6aeae4a64..bb9fc78299f5ddecdcc0f1baa66690879baef977 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/AdminCache.xml +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/AdminCache.xml @@ -9,6 +9,6 @@ <page name="AdminCache" area="Adminhtml" mca="admin/cache/" module="Magento_Backend"> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages .messages" strategy="css selector"/> <block name="actionsBlock" class="Magento\Backend\Test\Block\Cache" locator="div.page-actions" strategy="css selector"/> - <block name="additionalBlock" class="Magento\Backend\Test\Block\Cache\Additional" locator="div.fieldset.additional-cache-management" strategy="css selector"/> + <block name="additionalBlock" class="Magento\Backend\Test\Block\Cache\Additional" locator="div.additional-cache-management" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheFlushStaticFilesInProductionModeTest.php b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheFlushStaticFilesInProductionModeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9777614e554e1c5eee2a6b6538a84ff00f62bfa7 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheFlushStaticFilesInProductionModeTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Backend\Test\TestCase; + +use Magento\Mtf\TestCase\Injectable; +use Magento\Backend\Test\Page\Adminhtml\AdminCache; + +/** + * Steps: + * 1. Log in to backend. + * 2. Navigate through menu to cache management page. + * 3. Perform asserts. + * + * @ZephyrId MAGETWO-39934 + */ +class CacheFlushStaticFilesInProductionModeTest extends Injectable +{ + /* tags */ + const MVP = 'no'; + const DOMAIN = 'PS'; + /* end tags */ + + /** + * Test only for 'Flush Static Files Cache' in production mode. + * + * @return void + */ + public function __prepare() + { + if ($_ENV['mage_mode'] !== 'production') { + $this->markTestSkipped('Skip "Flush Static Files Cache" button absence test when not in production mode.'); + } + } + + /** + * Check 'Flush Static Files Cache' not visible in production mode. + * + * + * @param AdminCache $adminCache + * @return void + */ + public function test(AdminCache $adminCache) + { + $adminCache->open(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheFlushStaticFilesInProductionModeTest.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheFlushStaticFilesInProductionModeTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..7417721c749fb1f6accd5cda5c98375500a4e15c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheFlushStaticFilesInProductionModeTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\CacheFlushStaticFilesInProductionModeTest" summary="Flush Static Files Cache button not in production mode" ticketId="MAGETWO-39934"> + <variation name="FlushStaticFilesCacheInProductionTest"> + <constraint name="Magento\Backend\Test\Constraint\AssertCacheFlushStaticFilesInProductionMode" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.php b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.php index 6817abe742c6d4917f8b80963f67df4d06b75e50..c25b3d8922e9f6f2e6c265ad00650634a7fdb779 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.php @@ -12,11 +12,11 @@ use Magento\Backend\Test\Page\Adminhtml\AdminCache; /** * Steps: * 1. Log in to backend. - * 2. Navigate throught menu to cache management page. + * 2. Navigate through menu to cache management page. * 3. Click a button. * 4. Perform asserts. * - * @ZephyrId MAGETWO-34502, MAGETWO-34503, MAGETWO-39934 + * @ZephyrId MAGETWO-34052, MAGETWO-34053, MAGETWO-39934 */ class CacheManagementTest extends Injectable { @@ -26,7 +26,7 @@ class CacheManagementTest extends Injectable /* end tags */ /** - * Open admin cache management page. + * Open admin cache management page and click button to flush cache. * * @param AdminCache $adminCache * @param string $flushButtonName @@ -34,6 +34,12 @@ class CacheManagementTest extends Injectable */ public function test(AdminCache $adminCache, $flushButtonName) { + /** + * Skip test for 'Flush Static Files Cache' in production mode. + */ + if (($flushButtonName === 'Flush Static Files Cache') && $_ENV['mage_mode'] === 'production') { + $this->markTestSkipped('Skip flushing static files cache test when in production mode.'); + } $adminCache->open(); $adminCache->getAdditionalBlock()->clickFlushCache($flushButtonName); } diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.xml index e970b3f71fa5484226e5c222e599bce7f4dd00f8..f575bedde68835011423a03a3e9ffa5af0683607 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.xml +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/CacheManagementTest.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> - <testCase name="Magento\Backend\Test\TestCase\CacheManagementTest" summary="Configuration option on backend for singleQuery mode" ticketId="MAGETWO-34502"> + <testCase name="Magento\Backend\Test\TestCase\CacheManagementTest" summary="Additional Cache Management" ticketId="MAGETWO-34052"> <variation name="FlushCatalogImagesCacheTest"> <data name="flushButtonName" xsi:type="string">Flush Catalog Images Cache</data> <data name="successMessage" xsi:type="string">The image cache was cleaned.</data> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.php b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.php index 2e4509a1e66eab3cb43acfd0a9f0407c7bf50ffc..332f6e6dbb2ca40725b7d638445eda2f74bc4aae 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/NavigateMenuTest.php @@ -12,7 +12,7 @@ use Magento\Backend\Test\Page\Adminhtml\Dashboard; /** * Steps: * 1. Log in to backend. - * 2. Navigate throught menu to the page. + * 2. Navigate through menu to the page. * 3. Perform asserts. * * @ZephyrId MAGETWO-34874 diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Catalog/Product/View/Type/Bundle.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Catalog/Product/View/Type/Bundle.php index 696395534ddaa2f185490278de90689416efbe80..90e586768e40c27e697f32984fcf908b03730d97 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Catalog/Product/View/Type/Bundle.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Catalog/Product/View/Type/Bundle.php @@ -243,7 +243,7 @@ class Bundle extends Block */ protected function parseOptionText($optionText) { - preg_match('`^(.*?)\+ ?\$(\d.*?)$`', $optionText, $match); + preg_match('`^(.*?)\+ ?\$(\d.*?)$`sim', $optionText, $match); $optionPrice = isset($match[2]) ? str_replace(',', '', $match[2]) : 0; $optionTitle = isset($match[1]) ? trim($match[1]) : $optionText; diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleInCategory.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleInCategory.php index a70183ca9d5ddaabfa396dfcc60801dbbd0fe327..f800f5847c3a67202a729b3022e92649bb60c100 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleInCategory.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleInCategory.php @@ -31,7 +31,7 @@ class AssertBundleInCategory extends AssertProductInCategory //Price from/to verification $priceBlock = $catalogCategoryView->getListProductBlock()->getProductItem($bundle)->getPriceBlock(); - if ($bundle->hasData('special_price') || $bundle->hasData('group_price')) { + if ($bundle->hasData('special_price')) { $priceLow = $priceBlock->getPrice(); } else { $priceLow = ($bundle->getPriceView() == 'Price Range') diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleItemsOnProductPage.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleItemsOnProductPage.php index d0e6e7a7f2f695378a3a185f11f782be2257a45f..17fa6eaa8d0f883efb67c57c9d4e1c21f9eaf942 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleItemsOnProductPage.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleItemsOnProductPage.php @@ -70,11 +70,6 @@ class AssertBundleItemsOnProductPage extends AbstractAssertForm ? $assignedProduct['data']['selection_price_value'] : $bundleSelections['products'][$optionKey][$productKey]->getPrice(); - if ($product->hasData('group_price')) { - $groupedPrice = $product->getGroupPrice(); - $price -= $price / 100 * reset($groupedPrice)['price']; - } - $optionData['options'][$productKey] = [ 'title' => $assignedProduct['search_data']['name'], 'price' => number_format($price, 2), diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceType.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceType.php index a4346357991c559920ba7494fa539968f547fb1f..be0f886de0a7ad29c2fc4279782b9d9c13a13968 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceType.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceType.php @@ -69,7 +69,6 @@ class AssertBundlePriceType extends AbstractConstraint CheckoutCart $checkoutCartView, BundleProduct $originalProduct = null ) { - $customerGroup = 'NOT LOGGED IN'; $bundleData = $product->getData(); $this->productPriceType = $originalProduct !== null ? $originalProduct->getPriceType() @@ -79,10 +78,7 @@ class AssertBundlePriceType extends AbstractConstraint $checkoutCartView->open(); $cartItem = $checkoutCartView->getCartBlock()->getCartItem($product); $specialPrice = 0; - if (isset($bundleData['group_price'])) { - $specialPrice = - $bundleData['group_price'][array_search($customerGroup, $bundleData['group_price'])]['price'] / 100; - } + $optionPrice = []; $fillData = $product->getCheckoutData(); diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceView.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceView.php index 3a714a0115e867e49a19116f3df2b8b39ccb96bc..cb28099f1918b29685bc5bbe2d2a35d060878202 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceView.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundlePriceView.php @@ -49,7 +49,7 @@ class AssertBundlePriceView extends AbstractConstraint $priceView = $product->getPriceView(); $priceBlock = $catalogProductView->getViewBlock()->getPriceBlock(); - if ($product->hasData('special_price') || $product->hasData('group_price')) { + if ($product->hasData('special_price')) { $priceLow = $priceBlock->getPrice(); } else { $priceLow = ($priceView == 'Price Range') ? $priceBlock->getPriceFrom() : $priceBlock->getPrice(); diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php index 1ccbfd702f4ebc2ccc73aee8abae7f308ebde322..87baeeac0bed11c679c697b5026efa455814bfd2 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleProductPage.php @@ -26,7 +26,7 @@ class AssertBundleProductPage extends AssertProductPage $priceView = $this->product->getPriceView(); $priceBlock = $this->productView->getPriceBlock(); - if ($this->product->hasData('special_price') || $this->product->hasData('group_price')) { + if ($this->product->hasData('special_price')) { $priceLow = $priceBlock->getPrice(); } else { $priceLow = ($priceView == 'Price Range') ? $priceBlock->getPriceFrom() : $priceBlock->getPrice(); diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertGroupedPriceOnBundleProductPage.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertGroupedPriceOnBundleProductPage.php deleted file mode 100644 index 7d721ba3a304e8a1bf5b028c900a89988aa42fdf..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertGroupedPriceOnBundleProductPage.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Bundle\Test\Constraint; - -use Magento\Catalog\Test\Block\Product\View; -use Magento\Catalog\Test\Constraint\AssertProductGroupedPriceOnProductPage; -use Magento\Mtf\Fixture\FixtureInterface; - -/** - * Class AssertGroupedPriceOnBundleProductPage - */ -class AssertGroupedPriceOnBundleProductPage extends AssertProductGroupedPriceOnProductPage -{ - /** - * Get grouped price with fixture product and product page - * - * @param View $view - * @param FixtureInterface $product - * @return array - */ - protected function getGroupedPrice(View $view, FixtureInterface $product) - { - $groupPrice = [ - 'onPage' => [ - 'price_regular_price' => $view->getPriceBlock()->getPrice(), - 'price_from' => $view->getPriceBlock()->getPriceFrom(), - ], - 'fixture' => $product->getDataFieldConfig('price')['source']->getPriceData()['price_from'], - ]; - - $groupPrice['onPage'] = isset($groupPrice['onPage']['price_regular_price']) - ? str_replace('As low as $', '', $groupPrice['onPage']['price_regular_price']) - : str_replace('$', '', $groupPrice['onPage']['price_from']); - - return $groupPrice; - } -} diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/BundleProduct.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/BundleProduct.xml index 2cff47a580a63e3107126b560fe79b173f630938..8cd33885a7ddfc47830c30ee147d541408107991 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/BundleProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/BundleProduct.xml @@ -36,7 +36,6 @@ <field name="gallery" is_required="0" /> <field name="gift_message_available" is_required="0" group="autosettings" /> <field name="use_config_gift_message_available" is_required="0" group="autosettings" /> - <field name="group_price" is_required="0" group="advanced-pricing" repository="Magento\Catalog\Test\Repository\Product\GroupPriceOptions" /> <field name="has_options" is_required="0" /> <field name="image" is_required="0" /> <field name="image_label" is_required="0" /> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml index 2e920885fa4887a0d4133166a0dc5f7119cefae6..6912520e995e2c9f315df8b7793dfd1b5e85f1d4 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml @@ -7,50 +7,23 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/Repository/etc/repository.xsd"> <repository class="Magento\Bundle\Test\Repository\BundleProduct"> - <dataset name="BundleDynamic_sku_1073507449"> - <field name="sku" xsi:type="string">BundleDynamic_sku_10735074493</field> - <field name="name" xsi:type="string">BundleDynamic 1073507449</field> + <dataset name="default"> + <field name="name" xsi:type="string">Bundle dynamic product %isolation%</field> + <field name="url_key" xsi:type="string">bundle-dynamic-product-%isolation%</field> + <field name="sku" xsi:type="string">sku_bundle_dynamic_product_%isolation%</field> + <field name="sku_type" xsi:type="string">Dynamic</field> + <field name="price_type" xsi:type="string">Dynamic</field> <field name="price" xsi:type="array"> - <item name="price_from" xsi:type="string">1</item> - <item name="price_to" xsi:type="string">2</item> - </field> - <field name="short_description" xsi:type="string" /> - <field name="description" xsi:type="string"/> - <field name="tax_class_id" xsi:type="array"> - <item name="dataset" xsi:type="string">taxable_goods</item> + <item name="dataset" xsi:type="string">bundle_dynamic_with_category</item> </field> - <field name="sku_type" xsi:type="string">0</field> - <field name="price_type" xsi:type="string">0</field> - <field name="weight_type" xsi:type="string">0</field> - <field name="status" xsi:type="string">Product online</field> - <field name="shipment_type" xsi:type="string">1</field> - <field name="url_key" xsi:type="string">bundle-dynamic-product-%isolation%</field> - <field name="mtf_dataset_name" xsi:type="string">BundleDynamic_sku_1073507449</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> - </dataset> - - <dataset name="BundleDynamic_sku_215249172"> - <field name="sku" xsi:type="string">BundleDynamic_sku_215249172</field> - <field name="name" xsi:type="string">BundleDynamic 215249172</field> - <field name="price" xsi:type="array"> - <item name="price_from" xsi:type="string">3</item> - <item name="price_to" xsi:type="string">4</item> - </field> - <field name="short_description" xsi:type="string" /> - <field name="description" xsi:type="string" /> - <field name="tax_class_id" xsi:type="array"> - <item name="dataset" xsi:type="string">taxable_goods</item> + <field name="bundle_selections" xsi:type="array"> + <item name="dataset" xsi:type="string">default_dynamic</item> </field> - <field name="sku_type" xsi:type="string">0</field> - <field name="weight_type" xsi:type="string">0</field> - <field name="price_type" xsi:type="string">0</field> - <field name="shipment_type" xsi:type="string">1</field> - <field name="url_key" xsi:type="string">bundle-dynamic-product-%isolation%</field> - <field name="mtf_dataset_name" xsi:type="string">BundleDynamic_sku_215249172</field> - <field name="website_ids" xsi:type="array"> - <item name="0" xsi:type="string">Main Website</item> + <field name="checkout_data" xsi:type="array"> + <item name="dataset" xsi:type="string">bundle_default</item> </field> </dataset> @@ -109,6 +82,7 @@ </field> <field name="weight" xsi:type="string">1</field> <field name="weight_type" xsi:type="string">Fixed</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="shipment_type" xsi:type="string">Together</field> <field name="website_ids" xsi:type="array"> @@ -145,6 +119,7 @@ </field> <field name="weight" xsi:type="string">1</field> <field name="weight_type" xsi:type="string">Fixed</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -190,6 +165,7 @@ </field> <field name="weight" xsi:type="string">1</field> <field name="weight_type" xsi:type="string">Fixed</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -220,6 +196,7 @@ </field> <field name="weight" xsi:type="string">1</field> <field name="weight_type" xsi:type="string">Fixed</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="shipment_type" xsi:type="string">Together</field> <field name="website_ids" xsi:type="array"> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml index 9ab9547c3f7e0f3120d77237bdf3d6a93fe283c3..1654b19df2435385286d09db719cb4314f15fdba 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml @@ -114,7 +114,6 @@ <data name="product/data/weight" xsi:type="string">10</data> <data name="product/data/category" xsi:type="string">category_%isolation%</data> <data name="product/data/description" xsi:type="string">Bundle Product Fixed</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_20</data> <data name="product/data/price_view" xsi:type="string">As Low as</data> <data name="product/data/shipment_type" xsi:type="string">Separately</data> <data name="product/data/bundle_selections/dataset" xsi:type="string">all_types_fixed</data> @@ -217,7 +216,6 @@ <data name="product/data/sku" xsi:type="string">sku_bundle_dynamic_%isolation%</data> <data name="product/data/price_type" xsi:type="string">Dynamic</data> <data name="product/data/price/dataset" xsi:type="string">dynamic-32</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_20</data> <data name="product/data/bundle_selections/dataset" xsi:type="string">default_dynamic</data> <data name="product/data/bundle_selections/products" xsi:type="string">catalogProductSimple::product_100_dollar,catalogProductSimple::product_40_dollar</data> <data name="product/data/checkout_data/dataset" xsi:type="string">bundle_default</data> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..f86da3b3a9d699120caaeba904340e2e463d03fe --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\DeleteProductEntityTest"> + <variation name="DeleteProductEntityTestVariation4"> + <data name="products" xsi:type="string">bundleProduct::bundle_dynamic_product</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + <variation name="DeleteProductEntityTestVariation5"> + <data name="products" xsi:type="string">bundleProduct::bundle_fixed_product</data> + <data name="isRequired" xsi:type="string">No</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..475ac884e5d4ba04d217c016c3baad7f5cd42bb8 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\Checkout\Test\TestCase\DeleteProductFromMiniShoppingCartTest" summary="Delete Bundle Product from Mini Shopping Cart" ticketId="MAGETWO-29104"> + <variation name="DeleteBundleProductFromMiniShoppingCartTestVariation"> + <data name="products" xsi:type="string">bundleProduct::default</data> + <data name="deletedProductIndex" xsi:type="string">0</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab.php index e7f1c90383658114ea4466f1317ff321c2343b99..8b7671c7d719406d0e7b25c82bab846daa3dcdd2 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab.php @@ -20,7 +20,6 @@ class AdvancedPricingTab extends ProductTab * @var array */ protected $childrenForm = [ - 'group_price' => 'AdvancedPricingTab\OptionGroup', 'tier_price' => 'AdvancedPricingTab\OptionTier', ]; diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab/OptionGroup.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab/OptionGroup.php deleted file mode 100644 index 36ea46aaff06384131663ff5e6557a5b928f25b3..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab/OptionGroup.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Test\Block\Adminhtml\Product\Edit\AdvancedPricingTab; - -use Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Tab\Options\AbstractOptions; -use Magento\Mtf\Client\Element\SimpleElement; - -/** - * Class OptionField - * Form "Group prices" on the tab "Extended price" - */ -class OptionGroup extends AbstractOptions -{ - /** - * 'Add Group Price' button selector - * - * @var string - */ - protected $buttonFormLocator = "#group_prices_table tfoot button"; - - /** - * Fill the form 'Extended price' - * - * @param array $fields - * @param SimpleElement $element - * @return $this - */ - public function fillOptions(array $fields, SimpleElement $element = null) - { - $this->_rootElement->find($this->buttonFormLocator)->click(); - return parent::fillOptions($fields, $element); - } -} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab/OptionGroup.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab/OptionGroup.xml deleted file mode 100644 index 69c390b054415be42cf6ed15920fa702f8fee3a2..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/AdvancedPricingTab/OptionGroup.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<mapping strict="0"> - <fields> - <price> - <selector>[id$="_price"]</selector> - </price> - <website> - <selector>[id$="_website"]</selector> - <input>select</input> - </website> - <customer_group> - <selector>[id$="_cust_group"]</selector> - <input>select</input> - </customer_group> - </fields> -</mapping> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.xml index 7e94d4691cf15671e5d32e28a1bca688206aae1d..8fa1322b9aaf9d721d23c52aaf88fd5d4ac37180 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.xml @@ -27,9 +27,10 @@ <tax_class_id> <input>select</input> </tax_class_id> - <is_virtual> + <product_has_weight> + <selector>[name="product[product_has_weight]"]</selector> <input>checkbox</input> - </is_virtual> + </product_has_weight> <category_ids> <selector>#attribute-category_ids-container</selector> <class>Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Tab\ProductDetails\CategoryIds</class> @@ -78,9 +79,6 @@ <wrapper>product</wrapper> <fields> <special_price /> - <group_price> - <input>select</input> - </group_price> <tier_price> <input>select</input> </tier_price> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Links/CompareLink.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Links/CompareLink.php new file mode 100644 index 0000000000000000000000000000000000000000..6be91bbcecd43e037e2387a2029f2e8f26282e1a --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Links/CompareLink.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Block\Links; + +use Magento\Mtf\Block\Block; + +/** + * Products compare link block. + */ +class CompareLink extends Block +{ + /** + * Locator value for qty of Products in Compare list. + * + * @var string + */ + protected $qtyCompareProducts = '.compare .counter.qty'; + + /** + * Get qty of Products in Compare list. + * + * @return string + */ + public function getQtyInCompareList() + { + $this->waitForElementVisible($this->qtyCompareProducts); + $compareProductLink = $this->_rootElement->find($this->qtyCompareProducts); + preg_match_all('/^\d+/', $compareProductLink->getText(), $matches); + return $matches[0][0]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Price.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Price.php index 20d3e9d4f7cac01adb7bbebe555fcee453fb25fa..c8b8264673ca2e00d2783bc6eba98b42a6852d20 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Price.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Price.php @@ -27,7 +27,7 @@ class Price extends \Magento\Catalog\Test\Block\AbstractPriceBlock 'selector' => '.special-price .price', ], 'old_price' => [ - 'selector' => '.old-price .price-wrapper', + 'selector' => '.old-price .price-wrapper ', ], 'price_from' => [ 'selector' => '.price-from .price', diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php index 1002ab12091aa4284f5d745939cf5fb3c2204b24..95af5e8d573b95ae0998c6049ef8439077fbf474 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php @@ -140,6 +140,13 @@ class View extends AbstractConfigureBlock */ protected $successMessage = '[data-ui-id$=message-success]'; + /** + * Locator for page with ajax loading state. + * + * @var string + */ + protected $ajaxLoading = 'body.ajax-loading'; + /** * Get block price. * @@ -393,4 +400,14 @@ class View extends AbstractConfigureBlock { $this->_rootElement->find(sprintf($this->tabSelector, $name), Locator::SELECTOR_XPATH)->click(); } + + /** + * Wait loading block. + * + * @return void + */ + public function waitLoader() + { + $this->waitForElementNotVisible($this->ajaxLoading); + } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php index 9f5421ed86eadfc4812e8198fd8ace45a902cea5..7e4608bc008a1acf44807ac3937d283844f24fcc 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php @@ -23,21 +23,21 @@ class CustomOptions extends Form * * @var string */ - protected $optionsContext = '#product-options-wrapper > fieldset'; + protected $optionsContext = '#product-options-wrapper'; /** * Selector for single option block * * @var string */ - protected $optionElement = './div[contains(@class,"field")][%d]'; + protected $optionElement = '#product-options-wrapper .field'; /** * Selector for title of option * * @var string */ - protected $title = './/span[1]'; + protected $title = 'label'; /** * Selector for required option @@ -156,16 +156,13 @@ class CustomOptions extends Form protected function getListOptions() { $customOptions = []; - $context = $this->_rootElement->find($this->optionsContext); - $count = 1; - $optionElement = $context->find(sprintf($this->optionElement, $count), Locator::SELECTOR_XPATH); - while ($optionElement->isVisible()) { - $title = $optionElement->find($this->title, Locator::SELECTOR_XPATH)->getText(); + $optionElements = $this->_rootElement->getElements($this->optionElement); + foreach ($optionElements as $optionElement) { + $title = $optionElement->find($this->title)->getText(); $customOptions[$title] = $optionElement; - ++$count; - $optionElement = $context->find(sprintf($this->optionElement, $count), Locator::SELECTOR_XPATH); } + return $customOptions; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php index dd2265411c098a6e4998c25ffde26a82e069e61e..72b8853c3ad809dec0245ce6863d6d25e561c15f 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php @@ -11,54 +11,47 @@ use Magento\Mtf\Client\Locator; /** * Class Search - * Block for search field + * Block for "Search" section */ class Search extends Block { /** - * Selector matches found - "Suggest Search" + * Locator value for matches found - "Suggest Search". * * @var string */ protected $searchAutocomplete = './/div[@id="search_autocomplete"]//li[span[text()[normalize-space()="%s"]]]'; /** - * Selector number of matches for a given row + * Locator value for given row matches amount. * * @var string */ protected $searchItemAmount = '/span[contains(@class,"amount") and text()="%d"]'; /** - * Search field + * Locator value for "Search" field. * * @var string */ protected $searchInput = '#search'; /** - * Search button + * Locator value for "Search" button. * * @var string */ private $searchButton = '[title="Search"]'; /** - * Search button + * Locator value for "Search" button placeholder. * * @var string */ protected $placeholder = '//input[@id="search" and contains(@placeholder, "%s")]'; /** - * Css selector advanced search button - * - * @var string - */ - protected $advancedSearchSelector = '.action.advanced'; - - /** - * Search products by a keyword + * Perform search by a keyword. * * @param string $keyword * @return void @@ -68,37 +61,34 @@ class Search extends Block public function search($keyword) { $this->fillSearch($keyword); - $this->_rootElement->find($this->searchButton, Locator::SELECTOR_CSS)->click(); + $this->_rootElement->find($this->searchButton)->click(); } /** - * Fills the search field + * Fill "Search" field with correspondent text. * * @param string $text * @return void */ public function fillSearch($text) { - $this->_rootElement->find($this->searchInput, Locator::SELECTOR_CSS)->setValue($text); + $this->_rootElement->find($this->searchInput)->setValue($text); } /** - * Check that placeholder contains text + * Check if placeholder contains correspondent text or not. * * @param string $placeholderText * @return bool */ public function isPlaceholderContains($placeholderText) { - $field = $this->_rootElement->find( - sprintf($this->placeholder, $placeholderText), - Locator::SELECTOR_XPATH - ); + $field = $this->_rootElement->find(sprintf($this->placeholder, $placeholderText), Locator::SELECTOR_XPATH); return $field->isVisible(); } /** - * Checking block visibility "Suggest Search" + * Check if "Suggest Search" block visible or not. * * @param string $text * @param int|null $amount @@ -118,14 +108,4 @@ class Search extends Block } ); } - - /** - * Click advanced search button - * - * @return void - */ - public function clickAdvancedSearchButton() - { - $this->_rootElement->find($this->advancedSearchSelector)->click(); - } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeOptionsOnProductForm.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeOptionsOnProductForm.php index 60f0df41ed6bb3691ce512d632457720494b3e85..ec2f9acaaad0f1593d516e047e36dfad9c60997c 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeOptionsOnProductForm.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeOptionsOnProductForm.php @@ -6,28 +6,57 @@ namespace Magento\Catalog\Test\Constraint; +use Magento\Mtf\Fixture\InjectableFixture; use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Catalog\Test\Fixture\CatalogProductAttribute; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; /** - * Class AssertAttributeOptionsOnProductForm + * Assert all product attribute options on product creation form. */ class AssertAttributeOptionsOnProductForm extends AbstractConstraint { /** * Assert all product attribute options on product creation form. * + * @param InjectableFixture $product + * @param CatalogProductIndex $productGrid + * @param CatalogProductAttribute $attribute + * @param CatalogProductEdit $productEdit * @return void */ - public function processAssert() - { - // + public function processAssert( + InjectableFixture $product, + CatalogProductIndex $productGrid, + CatalogProductAttribute $attribute, + CatalogProductEdit $productEdit + ) { + $productGrid->open(); + $productGrid->getProductGrid()->searchAndOpen(['sku' => $product->getSku()]); + + $attributeOptions = $attribute->getOptions(); + $options[] = $attribute->getFrontendLabel(); + foreach ($attributeOptions as $option) { + $options[] = $option['admin']; + } + $productAttributeOptions = $productEdit->getProductForm()->getAttributeElement($attribute)->getText(); + $productOptions = explode("\n", $productAttributeOptions); + $diff = array_diff($options, $productOptions); + + \PHPUnit_Framework_Assert::assertTrue( + empty($diff), + "Products attribute options are absent on product form: " . implode(', ', $diff) + ); } /** + * Returns a string representation of the object. + * * @return string */ public function toString() { - // + return 'All product attribute options are visible on product creation form.'; } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeSearchableByLabel.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeSearchableByLabel.php deleted file mode 100644 index fc84ae52d8a084bfdfc65444dc50cd52a2f7ca49..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertAttributeSearchableByLabel.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Test\Constraint; - -use Magento\Mtf\Constraint\AbstractConstraint; - -/** - * Class AssertAttributeSearchableByLabel - */ -class AssertAttributeSearchableByLabel extends AbstractConstraint -{ - /** - * Assert that product attribute is searchable on Frontend. - * - * @return void - */ - public function processAssert() - { - // - } - - /** - * @return string - */ - public function toString() - { - // - } -} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterable.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterable.php index 1f355049a172ea7b076feb749db30b33bb33fa64..3314878854c52bcd8eeae25bc1b1b17e88656753 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterable.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterable.php @@ -53,7 +53,7 @@ class AssertProductAttributeIsFilterable extends AbstractConstraint ? $attribute->getManageFrontendLabel() : $attribute->getFrontendLabel(); \PHPUnit_Framework_Assert::assertTrue( - in_array($label, $catalogCategoryView->getLayeredNavigationBlock()->getFilters()), + in_array(strtoupper($label), $catalogCategoryView->getLayeredNavigationBlock()->getFilters()), 'Attribute is absent in layered navigation on category page.' ); } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterableInSearch.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterableInSearch.php index a19a5f881b4373848827007b190329c8eb58731f..8e3b69699f827127448d04610653bafb8dbf83b4 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterableInSearch.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsFilterableInSearch.php @@ -37,7 +37,7 @@ class AssertProductAttributeIsFilterableInSearch extends AbstractConstraint ? $attribute->getManageFrontendLabel() : $attribute->getFrontendLabel(); \PHPUnit_Framework_Assert::assertTrue( - in_array($label, $catalogCategoryView->getLayeredNavigationBlock()->getFilters()), + in_array(strtoupper($label), $catalogCategoryView->getLayeredNavigationBlock()->getFilters()), 'Attribute is absent in layered navigation on search page.' ); } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsUsedPromoRules.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsUsedPromoRules.php deleted file mode 100644 index a26a88439a0f53feeb7cbc1f62c78ab359e6e9f2..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductAttributeIsUsedPromoRules.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Test\Constraint; - -use Magento\Mtf\Constraint\AbstractConstraint; - -/** - * Class AssertProductAttributeIsUsedPromoRules - */ -class AssertProductAttributeIsUsedPromoRules extends AbstractConstraint -{ - /** - * Assert that product attribute can be used on promo rules conditions. - * - * @return void - */ - public function processAssert() - { - // - } - - /** - * @return string - */ - public function toString() - { - // - } -} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductCompareItemsLink.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductCompareItemsLink.php index 9dd4d24c9df1570a95297dae6c03fe46d2e3ffd8..b511bcd8968fbe8fd8c45c05f51a4b6c9aa1bc9a 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductCompareItemsLink.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductCompareItemsLink.php @@ -24,7 +24,7 @@ class AssertProductCompareItemsLink extends AbstractConstraint public function processAssert(array $products, CmsIndex $cmsIndex) { $productQty = count($products); - $qtyOnPage = $cmsIndex->getLinksBlock()->getQtyInCompareList(); + $qtyOnPage = $cmsIndex->getCompareLinkBlock()->getQtyInCompareList(); \PHPUnit_Framework_Assert::assertEquals( $productQty, diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductGroupedPriceOnProductPage.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductGroupedPriceOnProductPage.php deleted file mode 100644 index 62756906c842a175ef36e4dc1a83b1f51133389c..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductGroupedPriceOnProductPage.php +++ /dev/null @@ -1,107 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Test\Constraint; - -use Magento\Catalog\Test\Block\Product\View; -use Magento\Catalog\Test\Page\Product\CatalogProductView; -use Magento\Mtf\Client\BrowserInterface; -use Magento\Mtf\Constraint\AbstractConstraint; -use Magento\Mtf\Fixture\FixtureInterface; - -/** - * Class AssertProductGroupedPriceOnProductPage - */ -class AssertProductGroupedPriceOnProductPage extends AbstractConstraint implements AssertPriceOnProductPageInterface -{ - /** - * Error message - * - * @var string - */ - protected $errorMessage = 'That displayed grouped price on product page is NOT equal to one, passed from fixture.'; - - /** - * Customer group - * - * @var string - */ - protected $customerGroup; - - /** - * Assert that displayed grouped price on product page equals passed from fixture - * - * @param CatalogProductView $catalogProductView - * @param FixtureInterface $product - * @param BrowserInterface $browser - * @return void - */ - public function processAssert( - CatalogProductView $catalogProductView, - FixtureInterface $product, - BrowserInterface $browser - ) { - $browser->open($_ENV['app_frontend_url'] . $product->getUrlKey() . '.html'); - - //Process assertions - $this->assertPrice($product, $catalogProductView->getViewBlock()); - } - - /** - * Set $errorMessage for grouped price assert - * - * @param string $errorMessage - * @return void - */ - public function setErrorMessage($errorMessage) - { - $this->errorMessage = $errorMessage; - } - - /** - * Verify product special price on product view page - * - * @param FixtureInterface $product - * @param View $productViewBlock - * @param string $customerGroup [optional] - * @return void - */ - public function assertPrice(FixtureInterface $product, View $productViewBlock, $customerGroup = 'NOT LOGGED IN') - { - $this->customerGroup = $customerGroup; - $groupPrice = $this->getGroupedPrice($productViewBlock, $product); - \PHPUnit_Framework_Assert::assertEquals($groupPrice['fixture'], $groupPrice['onPage'], $this->errorMessage); - } - - /** - * Get grouped price with fixture product and product page - * - * @param View $view - * @param FixtureInterface $product - * @return array - */ - protected function getGroupedPrice(View $view, FixtureInterface $product) - { - $fields = $product->getData(); - $groupPrice['onPage'] = $view->getPriceBlock()->getSpecialPrice(); - $groupPrice['fixture'] = number_format( - $fields['group_price'][array_search($this->customerGroup, $fields['group_price'])]['price'], - 2 - ); - - return $groupPrice; - } - - /** - * Returns a string representation of the object - * - * @return string - */ - public function toString() - { - return 'Assert that displayed grouped price on product page equals passed from fixture.'; - } -} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php index a90a36c2078833463554094c890426b98e88ed65..a32cc115d534b0b1be8e1af221e56ca8b8e37efc 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductInCart.php @@ -64,15 +64,10 @@ class AssertProductInCart extends AbstractConstraint ? $checkoutData['options']['custom_options'] : []; $fixturePrice = $product->getPrice(); - $groupPrice = $product->getGroupPrice(); $specialPrice = $product->getSpecialPrice(); $cartItem = $checkoutCart->getCartBlock()->getCartItem($product); $formPrice = $cartItem->getPrice(); - if ($groupPrice) { - $groupPrice = reset($groupPrice); - $fixturePrice = $groupPrice['price']; - } if ($specialPrice) { $fixturePrice = $specialPrice; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductView.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductView.php index 1766ff0ee6677669ca0aa74e89a213e4c62ccadd..1414d6c21d1efbfe75bf03956ebabac2dc739d67 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductView.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductView.php @@ -72,11 +72,6 @@ class AssertProductView extends AbstractConstraint $priceComparing = $specialPrice; } - if ($groupPrice = $product->getGroupPrice()) { - $groupPrice = reset($groupPrice); - $priceComparing = $groupPrice['price']; - } - if ($priceComparing && isset($price['price_special_price'])) { \PHPUnit_Framework_Assert::assertEquals( number_format($priceComparing, 2), diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductAttribute.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductAttribute.xml index c2606db97bb6dbeb83d5c1f47d9b9f3d22ff48ad..6c9fb2a326500be7e266b1b2114085eed7765695 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductAttribute.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductAttribute.xml @@ -38,7 +38,7 @@ <field name="is_comparable" is_required="" group="frontend-properties" /> <field name="is_visible_on_front" is_required="" group="frontend-properties" /> <field name="is_html_allowed_on_front" is_required="" group="frontend-properties" /> - <field name="is_used_for_price_rules" is_required="" group="frontend-properties" /> + <field name="is_used_for_promo_rules" is_required="" group="frontend-properties" /> <field name="is_filterable_in_search" is_required="" group="frontend-properties" /> <field name="used_in_product_listing" is_required="" group="frontend-properties" /> <field name="used_for_sort_by" is_required="" group="frontend-properties" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple.xml index e295d379e33d2c977a9d2192ee40d4511694ce22..9e1006113ed4d37799dad125974499bcabe6cae6 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductSimple.xml @@ -35,7 +35,6 @@ <field name="description" is_required="0" group="product-details" /> <field name="gallery" is_required="0" /> <field name="gift_message_available" is_required="0" /> - <field name="group_price" is_required="0" group="advanced-pricing" repository="Magento\Catalog\Test\Repository\Product\GroupPriceOptions" /> <field name="has_options" is_required="0" /> <field name="image" is_required="0" /> <field name="image_label" is_required="0" /> @@ -85,7 +84,7 @@ <field name="cross_sell_products" group="crosssells" source="Magento\Catalog\Test\Fixture\Product\RelatedProducts" /> <field name="up_sell_products" group="upsells" source="Magento\Catalog\Test\Fixture\Product\RelatedProducts" /> <field name="related_products" group="related-products" source="Magento\Catalog\Test\Fixture\Product\RelatedProducts" /> - <field name="is_virtual" group="product-details" /> + <field name="product_has_weight" group="product-details" /> <field name="attributes" /> <field name="fpt" is_required="0" group="product-details" repository="Magento\Catalog\Test\Repository\Product\Fpt" /> </fixture> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductVirtual.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductVirtual.xml index edcfc39f2c5b08532d89fbc84807b2a661fda497..0ca75c7097d8a24430f8c268b4d9edde10bfbbcf 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductVirtual.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Fixture/CatalogProductVirtual.xml @@ -24,7 +24,7 @@ </item> <item name="input_prefix" xsi:type="string">product</item> </data_config> - <field name="is_virtual" group="product-details" /> + <field name="product_has_weight" group="product-details" /> <field name="category_ids" is_required="0" source="Magento\Catalog\Test\Fixture\Product\CategoryIds" group="product-details" /> <field name="color" is_required="0" /> <field name="country_of_manufacture" is_required="0" /> @@ -36,7 +36,6 @@ <field name="description" is_required="0" group="product-details" /> <field name="gallery" is_required="0" /> <field name="gift_message_available" is_required="0" /> - <field name="group_price" is_required="0" group="advanced-pricing" repository="Magento\Catalog\Test\Repository\Product\GroupPriceOptions" /> <field name="has_options" is_required="0" /> <field name="image" is_required="0" /> <field name="image_label" is_required="0" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php index 04baee216f3667fb923a20b9cec51ccf0f275294..169eb6805b3d38d984a20863933a6cc3f33d0272 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductAttribute/Curl.php @@ -42,7 +42,11 @@ class Curl extends AbstractCurl implements CatalogProductAttributeInterface 'No' => 0, 'Filterable (with results)' => 1, 'Filterable (no results)' => 2 - ] + ], + 'is_used_for_promo_rules' => [ + 'No' => 0, + 'Yes' => 1, + ], ]; /** @@ -59,10 +63,10 @@ class Curl extends AbstractCurl implements CatalogProductAttributeInterface if (isset($data['options'])) { foreach ($data['options'] as $key => $values) { + $index = 'option_' . $key; if ($values['is_default'] == 'Yes') { - $data['default'][] = $values['view']; + $data['default'][] = $index; } - $index = 'option_' . $key; $data['option']['value'][$index] = [$values['admin'], $values['view']]; $data['option']['order'][$index] = $key; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php index 56d819a6ed3989bf1f4a763db25e3eed579a0cdc..8ca825b8df56217bd1488e6f16401f403d19efeb 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php @@ -47,8 +47,9 @@ class Curl extends AbstractCurl implements CatalogProductSimpleInterface 'Yes' => 1, 'No' => 0 ], - 'is_virtual' => [ - 'Yes' => 1 + 'product_has_weight' => [ + 'Yes' => 1, + 'No' => 0, ], 'use_config_enable_qty_increments' => [ 'Yes' => 1, @@ -204,9 +205,6 @@ class Curl extends AbstractCurl implements CatalogProductSimpleInterface if (isset($fields['tier_price'])) { $fields['tier_price'] = $this->preparePriceData($fields['tier_price']); } - if (isset($fields['group_price'])) { - $fields['group_price'] = $this->preparePriceData($fields['group_price']); - } if (isset($fields['fpt'])) { $attributeLabel = $fixture->getDataFieldConfig('attribute_set_id')['source'] ->getAttributeSet()->getDataFieldConfig('assigned_attributes')['source'] @@ -249,10 +247,6 @@ class Curl extends AbstractCurl implements CatalogProductSimpleInterface $fields['affect_product_custom_options'] = 1; } - if (isset($fields['product']['weight'])) { - unset($fields['product']['is_virtual']); - } - return $fields; } @@ -410,6 +404,9 @@ class Curl extends AbstractCurl implements CatalogProductSimpleInterface */ protected function createProduct(array $data, array $config) { + $config['create_url_params']['set'] = isset($data['product']['attribute_set_id']) + ? $data['product']['attribute_set_id'] + : $config['create_url_params']['set']; $url = $this->getUrl($config); $curl = new BackendDecorator(new CurlTransport(), $this->_configuration); $curl->addOption(CURLOPT_HEADER, 1); diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/Category/Webapi.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/Category/Webapi.php index eaee68128cdac506c9909860b33778053e029a1c..340f0f0f8793a654095e6089e2aeb1be5fe2b661 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/Category/Webapi.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/Category/Webapi.php @@ -78,7 +78,7 @@ class Webapi extends AbstractWebapi implements CategoryInterface $this->webapiTransport->close(); if (empty($response['id'])) { - $this->eventManager->dispatchEvent(['curl_failed'], [$response]); + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); throw new \Exception('Category creation by webapi handler was not successful!'); } @@ -147,6 +147,7 @@ class Webapi extends AbstractWebapi implements CategoryInterface $this->webapiTransport->close(); if ('true' != $response) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); throw new \Exception('Assign product to category was not successful!'); } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/CmsIndex.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/CmsIndex.xml new file mode 100644 index 0000000000000000000000000000000000000000..c820d9985e3210b6dd8f70bbaf49db0de3cc7625 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/CmsIndex.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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="CmsIndex" mca="cms/index/index"> + <block name="compareProductsBlock" class="Magento\Catalog\Test\Block\Product\Compare\Sidebar" locator=".sidebar.sidebar-additional" strategy="css selector" /> + <block name="compareLinkBlock" class="Magento\Catalog\Test\Block\Links\CompareLink" locator="[data-role='compare-products-link']" strategy="css selector" /> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogAttributeSet.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogAttributeSet.xml index 3d89c92046b048bb62f39a09f44ffeea350a9ffe..e51d91e2a6d862518511ff8bb5deb8973d2edbdc 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogAttributeSet.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogAttributeSet.xml @@ -28,5 +28,15 @@ <item name="dataset" xsi:type="string">attribute_type_fpt</item> </field> </dataset> + + <dataset name="custom_attribute_set_with_colors"> + <field name="attribute_set_name" xsi:type="string">Custom_attribute_set_with_colors_%isolation%</field> + <field name="skeleton_set" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </field> + <field name="assigned_attributes" xsi:type="array"> + <item name="dataset" xsi:type="string">color_for_promo_rules</item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductAttribute.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductAttribute.xml index 82f92677fd218755c054785d7bc40ccf9ed8eea0..9ef0bcfd31196057111439f69587c7f090dc53b0 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductAttribute.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductAttribute.xml @@ -96,6 +96,31 @@ </field> </dataset> + <dataset name="color_for_promo_rules"> + <field name="frontend_label" xsi:type="string">color_%isolation%</field> + <field name="attribute_code" xsi:type="string">color_%isolation%</field> + <field name="frontend_input" xsi:type="string">Dropdown</field> + <field name="is_required" xsi:type="string">No</field> + <field name="is_used_for_promo_rules" xsi:type="string">Yes</field> + <field name="options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="is_default" xsi:type="string">Yes</item> + <item name="admin" xsi:type="string">red</item> + <item name="view" xsi:type="string">red_%isolation%</item> + </item> + <item name="1" xsi:type="array"> + <item name="is_default" xsi:type="string">No</item> + <item name="admin" xsi:type="string">green</item> + <item name="view" xsi:type="string">green_%isolation%</item> + </item> + <item name="2" xsi:type="array"> + <item name="is_default" xsi:type="string">No</item> + <item name="admin" xsi:type="string">blue</item> + <item name="view" xsi:type="string">blue_%isolation%</item> + </item> + </field> + </dataset> + <dataset name="size"> <field name="frontend_label" xsi:type="string">size_%isolation%</field> <field name="attribute_code" xsi:type="string">size_%isolation%</field> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml index 831681a82f09b50630176ca985df2dfc6e48159b..6be4a2275e35365d10655a382d0fdb93b265f618 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml @@ -13,6 +13,34 @@ </field> <field name="name" xsi:type="string">Simple Product %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> + <field name="weight" xsi:type="string">1</field> + <field name="quantity_and_stock_status" xsi:type="array"> + <item name="qty" xsi:type="string">25</item> + <item name="is_in_stock" xsi:type="string">In Stock</item> + </field> + <field name="price" xsi:type="array"> + <item name="value" xsi:type="string">560</item> + </field> + <field name="tax_class_id" xsi:type="array"> + <item name="dataset" xsi:type="string">taxable_goods</item> + </field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="string">Main Website</item> + </field> + <field name="visibility" xsi:type="string">Catalog, Search</field> + <field name="url_key" xsi:type="string">simple-product-%isolation%</field> + <field name="checkout_data" xsi:type="array"> + <item name="dataset" xsi:type="string">simple_order_default</item> + </field> + </dataset> + + <dataset name="product_with_special_symbols_in_name"> + <field name="attribute_set_id" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </field> + <field name="name" xsi:type="string">Product \'!@#$%^&*()+:;\\|}{][?=-~` %isolation%</field> + <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> <field name="is_virtual" xsi:type="string">No</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> @@ -35,13 +63,40 @@ </field> </dataset> + <dataset name="product_with_custom_color_attribute"> + <field name="name" xsi:type="string">Simple Product %isolation%</field> + <field name="sku" xsi:type="string">simple_product_%isolation%</field> + <field name="attribute_set_id" xsi:type="array"> + <item name="dataset" xsi:type="string">custom_attribute_set_with_colors</item> + </field> + <field name="quantity_and_stock_status" xsi:type="array"> + <item name="qty" xsi:type="string">25</item> + <item name="is_in_stock" xsi:type="string">In Stock</item> + </field> + <field name="weight" xsi:type="string">1</field> + <field name="price" xsi:type="array"> + <item name="value" xsi:type="string">560</item> + <item name="dataset" xsi:type="string" /> + </field> + <field name="category_ids" xsi:type="array"> + <item name="dataset" xsi:type="string">default_subcategory</item> + </field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="string">Main Website</item> + </field> + <field name="checkout_data" xsi:type="array"> + <item name="dataset" xsi:type="string">simple_order_default</item> + </field> + <field name="url_key" xsi:type="string">simple-product-%isolation%</field> + </dataset> + <dataset name="product_10_dollar"> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> </field> <field name="name" xsi:type="string">product_10_dollar %isolation%</field> <field name="sku" xsi:type="string">sku_product_10_dollar_%isolation%</field> - <field name="is_virtual" xsi:type="string">No</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1000</item> @@ -69,7 +124,7 @@ </field> <field name="name" xsi:type="string">product_20_dollar %isolation%</field> <field name="sku" xsi:type="string">sku_product_20_dollar_%isolation%</field> - <field name="is_virtual" xsi:type="string">No</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1000</item> @@ -91,7 +146,7 @@ <dataset name="product_with_url_key"> <field name="name" xsi:type="string">Simple Product %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> - <field name="is_virtual" xsi:type="string">No</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -116,6 +171,7 @@ </field> <field name="name" xsi:type="string">Simple Product %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1000</item> @@ -143,6 +199,7 @@ </field> <field name="name" xsi:type="string">Simple Product %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -171,6 +228,7 @@ <item name="qty" xsi:type="number">666.0000</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -195,6 +253,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -219,6 +278,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">100</item> @@ -241,6 +301,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -263,6 +324,7 @@ <field name="name" xsi:type="string">Simple product without category %isolation%</field> <field name="url_key" xsi:type="string">simple-product-%isolation%</field> <field name="sku" xsi:type="string">simple_product_without_category_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -290,6 +352,7 @@ <item name="value" xsi:type="string">100</item> <item name="dataset" xsi:type="string" /> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">100</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> @@ -316,6 +379,7 @@ <item name="qty" xsi:type="string">111</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="status" xsi:type="string">Product online</field> <field name="website_ids" xsi:type="array"> @@ -340,6 +404,7 @@ <item name="value" xsi:type="string">50</item> <item name="dataset" xsi:type="string" /> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">50</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> @@ -356,6 +421,7 @@ <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">100</item> <item name="dataset" xsi:type="string" /> @@ -376,6 +442,7 @@ <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">10</item> <item name="dataset" xsi:type="string" /> @@ -403,6 +470,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="description" xsi:type="string"><p>dfj_full</p></field> <field name="status" xsi:type="string">Product online</field> @@ -426,6 +494,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">0.1</field> <field name="description" xsi:type="string"><p>Simple with Weight 0.1</p></field> <field name="status" xsi:type="string">Product online</field> @@ -449,6 +518,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">150.1</field> <field name="description" xsi:type="string"><p>Simple with Weight 150.1</p></field> <field name="status" xsi:type="string">Product online</field> @@ -472,6 +542,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="description" xsi:type="string"><p>adc_Full</p></field> <field name="status" xsi:type="string">Product online</field> @@ -493,6 +564,7 @@ <item name="qty" xsi:type="string">25</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">100</item> @@ -512,49 +584,12 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">100</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="url_key" xsi:type="string">simple-product-%isolation%</field> <field name="special_price" xsi:type="string">9</field> </dataset> - <dataset name="simple_with_group_price"> - <field name="attribute_set_id" xsi:type="array"> - <item name="dataset" xsi:type="string">default</item> - </field> - <field name="name" xsi:type="string">Simple Product %isolation%</field> - <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> - <field name="price" xsi:type="array"> - <item name="value" xsi:type="string">100</item> - </field> - <field name="weight" xsi:type="string">1</field> - <field name="group_price" xsi:type="array"> - <item name="dataset" xsi:type="string">not_logged_in_20</item> - </field> - <field name="url_key" xsi:type="string">simple-product-%isolation%</field> - </dataset> - - <dataset name="simple_with_group_price_and_category"> - <field name="attribute_set_id" xsi:type="array"> - <item name="dataset" xsi:type="string">default</item> - </field> - <field name="name" xsi:type="string">Simple Product %isolation%</field> - <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> - <field name="price" xsi:type="array"> - <item name="value" xsi:type="string">100</item> - </field> - <field name="weight" xsi:type="string">1</field> - <field name="group_price" xsi:type="array"> - <item name="dataset" xsi:type="string">general_90_99</item> - </field> - <field name="category_ids" xsi:type="array"> - <item name="dataset" xsi:type="string">default_subcategory</item> - </field> - <field name="website_ids" xsi:type="array"> - <item name="0" xsi:type="string">Main Website</item> - </field> - <field name="url_key" xsi:type="string">simple-product-%isolation%</field> - </dataset> - <dataset name="simple_with_tier_price"> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -564,6 +599,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="tier_price" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -580,6 +616,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="tier_price" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -602,6 +639,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> @@ -624,6 +662,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="custom_options" xsi:type="array"> <item name="dataset" xsi:type="string">drop_down_with_one_option_percent_price</item> @@ -649,6 +688,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="url_key" xsi:type="string">simple-product-%isolation%</field> <field name="custom_options" xsi:type="array"> @@ -667,6 +707,7 @@ <item name="use_config_notify_stock_qty" xsi:type="string">No</item> <item name="notify_stock_qty" xsi:type="string">2</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -686,6 +727,7 @@ </field> <field name="name" xsi:type="string">Simple Product out of stock %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_out_of_stock%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -713,6 +755,7 @@ </field> <field name="name" xsi:type="string">Simple Product offline %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_offline_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -741,6 +784,7 @@ </field> <field name="name" xsi:type="string">Simple Product not visible %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_not_visible_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -768,6 +812,7 @@ </field> <field name="name" xsi:type="string">Simple Product with cart limit %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_with_cart_limit_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -802,6 +847,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="custom_options" xsi:type="array"> <item name="dataset" xsi:type="string">drop_down_with_one_option_percent_price</item> @@ -821,6 +867,7 @@ </field> <field name="name" xsi:type="string">Simple Product with qty increments %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_with_qty_increments_%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -855,6 +902,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">300</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> @@ -876,6 +924,7 @@ <item name="qty" xsi:type="string">666</item> <item name="is_in_stock" xsi:type="string">In Stock</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="attribute_set_id" xsi:type="array"> <item name="dataset" xsi:type="string">default</item> @@ -912,6 +961,7 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">70</item> </field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="custom_options" xsi:type="array"> <item name="dataset" xsi:type="string">drop_down_with_one_option_fixed_price</item> @@ -944,6 +994,7 @@ <item name="value" xsi:type="string">110</item> </field> <field name="special_price" xsi:type="string">100</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="url_key" xsi:type="string">simple-product-%isolation%</field> <field name="fpt" xsi:type="array"> @@ -958,6 +1009,7 @@ <field name="name" xsi:type="string">Simple Product %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> <field name="url_key" xsi:type="string">simple-product-%isolation%</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="weight" xsi:type="string">1</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1000</item> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual.xml index 818590b22319a1de8c03a62887e5b1e9ec8b3721..582501462c3c3acbf624d16afc625655fffc2909 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual.xml @@ -15,7 +15,7 @@ <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="attribute_set_id" xsi:type="array"> @@ -39,7 +39,7 @@ <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="name" xsi:type="string">Virtual product %isolation%</field> <field name="sku" xsi:type="string">sku_virtual_product_%isolation%</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">10</item> </field> @@ -55,7 +55,7 @@ <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="name" xsi:type="string">Virtual product %isolation%</field> <field name="sku" xsi:type="string">sku_virtual_product_%isolation%</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">10</item> </field> @@ -78,7 +78,7 @@ <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="attribute_set_id" xsi:type="array"> @@ -106,7 +106,7 @@ <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="attribute_set_id" xsi:type="array"> @@ -136,7 +136,7 @@ <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="attribute_set_id" xsi:type="array"> @@ -161,7 +161,7 @@ <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="url_key" xsi:type="string">virtual-product%isolation%</field> <field name="name" xsi:type="string">Virtual product %isolation%</field> <field name="sku" xsi:type="string">sku_virtual_product_%isolation%</field> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual/CheckoutData.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual/CheckoutData.xml index 7a9a25ede99799eb8a9833ee558efb6b61142d0b..8eb6be9661f11a23e0458b13b1182666914a3a99 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual/CheckoutData.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductVirtual/CheckoutData.xml @@ -31,5 +31,9 @@ <item name="custom_price" xsi:type="string">100</item> </field> </dataset> + + <dataset name="virtual_update_mini_shopping_cart"> + <field name="qty" xsi:type="string">2</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/GroupPriceOptions.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/GroupPriceOptions.xml deleted file mode 100644 index c55de6d823b8c81bf52abce6e3866445d2a999ba..0000000000000000000000000000000000000000 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Product/GroupPriceOptions.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" ?> -<!-- -/** - * Copyright © 2015 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/Magento/Mtf/Repository/etc/repository.xsd"> - <repository class="Magento\Catalog\Test\Repository\Product\GroupPriceOptions"> - <dataset name="not_logged_in_20"> - <field name="0" xsi:type="array"> - <item name="price" xsi:type="string">20</item> - <item name="website" xsi:type="string">All Websites [USD]</item> - <item name="customer_group" xsi:type="string">NOT LOGGED IN</item> - </field> - </dataset> - - <dataset name="not_logged_in_90"> - <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="customer_group" xsi:type="string">NOT LOGGED IN</item> - </field> - </dataset> - - <dataset name="general_90_99"> - <field name="0" xsi:type="array"> - <item name="price" xsi:type="string">90.99</item> - <item name="website" xsi:type="string">All Websites [USD]</item> - <item name="customer_group" xsi:type="string">General</item> - </field> - </dataset> - </repository> -</config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AbstractCompareProductsTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AbstractCompareProductsTest.php index e3940285daa737f105639664a6c4498075dd75a2..5fadf342d10eab9dbf5c643c6e745c225d99e54c 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AbstractCompareProductsTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AbstractCompareProductsTest.php @@ -10,7 +10,6 @@ use Magento\Catalog\Test\Page\Product\CatalogProductCompare; use Magento\Catalog\Test\Page\Product\CatalogProductView; use Magento\Cms\Test\Page\CmsIndex; use Magento\Customer\Test\Fixture\Customer; -use Magento\Customer\Test\Page\CustomerAccountLogin; use Magento\Mtf\Client\BrowserInterface; use Magento\Mtf\Constraint\AbstractConstraint; use Magento\Mtf\Fixture\FixtureFactory; @@ -59,13 +58,6 @@ abstract class AbstractCompareProductsTest extends Injectable */ protected $catalogProductView; - /** - * Customer login page. - * - * @var CustomerAccountLogin - */ - protected $customerAccountLogin; - /** * Fixture factory. * @@ -100,18 +92,15 @@ abstract class AbstractCompareProductsTest extends Injectable * @param CmsIndex $cmsIndex * @param CatalogProductView $catalogProductView * @param BrowserInterface $browser - * @param CustomerAccountLogin $customerAccountLogin * @return void */ public function __inject( CmsIndex $cmsIndex, CatalogProductView $catalogProductView, - BrowserInterface $browser, - CustomerAccountLogin $customerAccountLogin + BrowserInterface $browser ) { $this->cmsIndex = $cmsIndex; $this->catalogProductView = $catalogProductView; - $this->customerAccountLogin = $customerAccountLogin; $this->browser = $browser; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddCompareProductsTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddCompareProductsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a14088898d976107c3c22773e7461296037f0fed --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddCompareProductsTest.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\TestCase\Product; + +use Magento\Catalog\Test\Constraint\AssertProductCompareSuccessAddMessage; +use Magento\Catalog\Test\Page\Product\CatalogProductCompare; + +/** + * Preconditions: + * 1. All product types are created. + * 2. Customer created. + * + * Steps: + * 1. Navigate to front-end. + * 1.1 If present data for customer, login as test customer. + * 2. Open product page of test product(s) and click "Add to Compare" button. + * 3. Assert success message is present on page. + * 4. Navigate to compare page(click "compare product" link at the top of the page). + * 5. Perform all asserts. + * + * @group Compare_Products_(MX) + * @ZephyrId MAGETWO-25843 + */ +class AddCompareProductsTest extends AbstractCompareProductsTest +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'MX'; + /* end tags */ + + /** + * Catalog product compare page. + * + * @var CatalogProductCompare + */ + protected $catalogProductCompare; + + /** + * Test creation for adding compare products. + * + * @param string $products + * @param string $isCustomerLoggedIn + * @param AssertProductCompareSuccessAddMessage $assertProductCompareSuccessAddMessage + * @param CatalogProductCompare $catalogProductCompare + * @return array + */ + public function test( + $products, + $isCustomerLoggedIn, + AssertProductCompareSuccessAddMessage $assertProductCompareSuccessAddMessage, + CatalogProductCompare $catalogProductCompare + ) { + //Steps + $this->catalogProductCompare = $catalogProductCompare; + $this->cmsIndex->open(); + if ($isCustomerLoggedIn == 'Yes') { + $this->loginCustomer(); + } + $this->products = $this->createProducts($products); + $this->addProducts($this->products, $assertProductCompareSuccessAddMessage); + $this->cmsIndex->getLinksBlock()->openLink("Compare Products"); + + return ['products' => $this->products]; + } + + /** + * Clear data after test. + * + * @return void + */ + public function tearDown() + { + $this->cmsIndex->open(); + $this->cmsIndex->getLinksBlock()->openLink("Compare Products"); + for ($i = 1; $i <= count($this->products); $i++) { + $this->catalogProductCompare->getCompareProductsBlock()->removeProduct(); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddCompareProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddCompareProductsTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..7e106eb611f86ddb15c13da1611ed7bbbc64d3f0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddCompareProductsTest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\AddCompareProductsTest" summary="Add Products to Compare" ticketId="MAGETWO-25843"> + <variation name="AddCompareProductsTestVariation1"> + <data name="products" xsi:type="string">catalogProductSimple::simple_for_composite_products</data> + <data name="isCustomerLoggedIn" xsi:type="string">No</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareItemsLink" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductComparePage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareBlockOnCmsPage" /> + </variation> + <variation name="AddCompareProductsTestVariation2"> + <data name="products" xsi:type="string">catalogProductSimple::simple_for_composite_products,bundleProduct::bundle_dynamic_product</data> + <data name="isCustomerLoggedIn" xsi:type="string">No</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareItemsLink" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductComparePage" /> + </variation> + <variation name="AddCompareProductsTestVariation3"> + <data name="products" xsi:type="string">configurableProduct::default</data> + <data name="isCustomerLoggedIn" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareItemsLink" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductComparePage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareBlockOnCmsPage" /> + </variation> + <variation name="AddCompareProductsTestVariation4"> + <data name="products" xsi:type="string">catalogProductSimple::simple_for_composite_products,catalogProductVirtual::default,downloadableProduct::default,groupedProduct::grouped_product_with_price,configurableProduct::default,bundleProduct::bundle_dynamic_product,bundleProduct::bundle_fixed_product</data> + <data name="isCustomerLoggedIn" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareItemsLink" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductComparePage" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3556fa00a838820d65e20d642037d4260e344bc3 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\TestCase\Product; + +use Magento\Config\Test\Fixture\ConfigData; +use Magento\Customer\Test\Page\CustomerAccountIndex; + +/** + * Preconditions: + * 1. All product types are created. + * 2. Customer created. + * + * Steps: + * 1. Login to frontend. + * 2. Add to Compare Product $products. + * 3. Navigate to My Account page. + * 4. Click "Clear All" icon under the left menu tabs. + * 5. Perform assertions. + * + * @group Compare_Products_(MX) + * @ZephyrId MAGETWO-25961 + */ +class ClearAllCompareProductsTest extends AbstractCompareProductsTest +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'MX'; + /* end tags */ + + /** + * Test creation for clear all compare products. + * + * @param string $products + * @param ConfigData $config + * @param CustomerAccountIndex $customerAccountIndex + * @return void + */ + public function test($products, ConfigData $config, CustomerAccountIndex $customerAccountIndex) + { + // Preconditions + $config->persist(); + $products = $this->createProducts($products); + + //Steps + $this->cmsIndex->open(); + $this->loginCustomer(); + $this->addProducts($products); + $this->cmsIndex->getLinksBlock()->openLink("My Account"); + $customerAccountIndex->getCompareProductsBlock()->clickClearAll(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9fa3f36b002f74ec2d77bc1b2a0b53fc6e611bd --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\ClearAllCompareProductsTest" summary="Clear All Compare Products" ticketId="MAGETWO-25961"> + <variation name="ClearAllCompareProductsTestVariation1"> + <data name="config/dataset" xsi:type="string">compare_products</data> + <data name="products" xsi:type="string">catalogProductSimple::simple_for_composite_products,catalogProductVirtual::default,downloadableProduct::default,groupedProduct::grouped_product_with_price,configurableProduct::default,bundleProduct::bundle_dynamic_product,bundleProduct::bundle_fixed_product</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareSuccessRemoveAllProductsMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareItemsLinkIsAbsent" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareRemoveLastProductMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotVisibleInCompareBlock" /> + </variation> + </testCase> +</config> 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 95424b11ab4032417e8b29b0668b61d81f38fda9..f46eff7dac4b8327c03897fbc0a3cbf41d78c166 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 @@ -98,7 +98,6 @@ <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/price/dataset" xsi:type="string">MAGETWO-23030</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCategory" /> @@ -118,7 +117,6 @@ <data name="product/data/custom_options/dataset" xsi:type="string">drop_down_with_one_option_fixed_price</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">MAGETWO-23029</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInCategory" /> @@ -267,7 +265,6 @@ <data name="product/data/description" xsi:type="string">Simple Product description %isolation%</data> <data name="product/data/weight" xsi:type="string">64</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">141</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_20</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml index df25c659478dd74b2a51cf6310de409821728db5..395b421268e0bf96b0be6b9210fc3522e4e8ce54 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml @@ -13,7 +13,7 @@ <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">10</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> @@ -25,7 +25,7 @@ <data name="product/data/price/value" xsi:type="string">10</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category_%isolation%</data> <data name="product/data/tier_price/dataset" xsi:type="string">MAGETWO-23002</data> <data name="product/data/inventory_manage_stock" xsi:type="string">Yes</data> @@ -40,16 +40,14 @@ <data name="description" xsi:type="string">Create product with out of stock</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> - <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">10</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/price/dataset" xsi:type="string">MAGETWO-23030</data> - <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> <data name="product/data/visibility" xsi:type="string">Search</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSkuAutoGenerated" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> @@ -59,9 +57,8 @@ <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">10</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category_%isolation%</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> <data name="product/data/visibility" xsi:type="string">Catalog</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> @@ -73,8 +70,7 @@ <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">9000</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/custom_options/dataset" xsi:type="string">options_suite</data> <data name="product/data/custom_options/import_products" xsi:type="string">catalogProductSimple::with_two_custom_option,catalogProductSimple::with_all_custom_option</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> @@ -90,7 +86,7 @@ <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/price/dataset" xsi:type="string">MAGETWO-23030</data> <data name="product/data/inventory_manage_stock" xsi:type="string">No</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> @@ -106,7 +102,7 @@ <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">9000</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/tier_price/dataset" xsi:type="string">default</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> @@ -120,7 +116,7 @@ <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">10</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category_%isolation%</data> <data name="tag" xsi:type="string">test_type:acceptance_test</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteCompareProductsTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteCompareProductsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cf945dac7cbb8aa9a55e7937cc9050e39f841667 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteCompareProductsTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\TestCase\Product; + +use Magento\Catalog\Test\Page\Product\CatalogProductCompare; +use Magento\Customer\Test\Fixture\Customer; +use Magento\Mtf\Fixture\FixtureFactory; + +/** + * Preconditions: + * 1. All product types are created. + * 2. Customer created. + * + * Steps: + * 1. Add to Compare Product $products. + * 2. Navigate to Compare Product page. + * 3. Click (X) icon near the $product from dataset. + * 4. Perform assertions. + * + * @group Compare_Products_(MX) + * @ZephyrId MAGETWO-26161 + */ +class DeleteCompareProductsTest extends AbstractCompareProductsTest +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'MX'; + /* end tags */ + + /** + * Catalog product compare page. + * + * @var CatalogProductCompare + */ + protected $catalogProductCompare; + + /** + * Prepare data. + * + * @param FixtureFactory $fixtureFactory + * @param Customer $customer + * @return void + */ + public function __prepare(FixtureFactory $fixtureFactory, Customer $customer) + { + parent::__prepare($fixtureFactory, $customer); + $config = $this->fixtureFactory->createByCode('configData', ['dataset' => 'compare_products']); + $config->persist(); + } + + /** + * Test creation for delete product from compare products list. + * + * @param string $products + * @param string $removeProductIndex + * @param string $isCustomerLoggedIn + * @param CatalogProductCompare $catalogProductCompare + * @return array + */ + public function test( + $products, + $removeProductIndex, + $isCustomerLoggedIn, + CatalogProductCompare $catalogProductCompare + ) { + //Steps + $this->catalogProductCompare = $catalogProductCompare; + $this->cmsIndex->open(); + if ($isCustomerLoggedIn == 'Yes') { + $this->loginCustomer(); + } + $this->products = $this->createProducts($products); + $this->addProducts($this->products); + $this->cmsIndex->getLinksBlock()->openLink("Compare Products"); + $this->catalogProductCompare->getCompareProductsBlock()->removeProduct($removeProductIndex); + + return ['product' => $this->products[$removeProductIndex - 1], 'countProducts' => count($this->products)]; + } + + /** + * Clear data after test. + * + * @return void + */ + public function tearDown() + { + if (count($this->products) > 1) { + $this->cmsIndex->open(); + $this->cmsIndex->getLinksBlock()->openLink("Compare Products"); + $this->catalogProductCompare->getCompareProductsBlock()->removeAllProducts(); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteCompareProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteCompareProductsTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..4a9f009213c4a195d03cc300b23b4fe03b5a691e --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteCompareProductsTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\DeleteCompareProductsTest" summary="Delete Compare Products" ticketId="MAGETWO-26161"> + <variation name="DeleteCompareProductsTestVariation1"> + <data name="products" xsi:type="string">catalogProductSimple::simple_for_composite_products,catalogProductVirtual::default,downloadableProduct::default,groupedProduct::grouped_product_with_price,configurableProduct::default,bundleProduct::bundle_dynamic_product,bundleProduct::bundle_fixed_product</data> + <data name="removeProductIndex" xsi:type="string">1</data> + <data name="isCustomerLoggedIn" xsi:type="string">No</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareSuccessRemoveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotVisibleInComparePage" /> + </variation> + <variation name="DeleteCompareProductsTestVariation2"> + <data name="products" xsi:type="string">catalogProductSimple::simple_for_composite_products,catalogProductVirtual::default,downloadableProduct::default,groupedProduct::grouped_product_with_price,configurableProduct::default,bundleProduct::bundle_dynamic_product,bundleProduct::bundle_fixed_product</data> + <data name="removeProductIndex" xsi:type="string">6</data> + <data name="isCustomerLoggedIn" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareSuccessRemoveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotVisibleInComparePage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotVisibleInCompareBlock" /> + </variation> + <variation name="DeleteCompareProductsTestVariation3"> + <data name="products" xsi:type="string">bundleProduct::bundle_dynamic_product</data> + <data name="removeProductIndex" xsi:type="string">1</data> + <data name="isCustomerLoggedIn" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareSuccessRemoveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareRemoveLastProductMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotVisibleInCompareBlock" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductCompareItemsLinkIsAbsent" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.php new file mode 100644 index 0000000000000000000000000000000000000000..98c9c91b7e455f6e86a78696f4ddfcfac33848c2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\TestCase\Product; + +use Magento\Catalog\Test\Fixture\Category; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; +use Magento\Mtf\Fixture\FixtureFactory; +use Magento\Mtf\TestCase\Injectable; + +/** + * Preconditions: + * 1. Create product according to data set. + * + * Steps: + * 1. Login to backend. + * 2. Navigate Products->Catalog. + * 3. Select products created in preconditions. + * 4. Select delete from mass-action. + * 5. Submit form. + * 6. Perform asserts. + * + * @group Products_(MX) + * @ZephyrId MAGETWO-23272 + */ +class DeleteProductEntityTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'MX'; + /* end tags */ + + /** + * Product page with a grid. + * + * @var CatalogProductIndex + */ + protected $catalogProductIndex; + + /** + * Prepare data. + * + * @param Category $category + * @return array + */ + public function __prepare(Category $category) + { + $category->persist(); + return [ + 'category' => $category + ]; + } + + /** + * Injection data. + * + * @param CatalogProductIndex $catalogProductIndexPage + * @return void + */ + public function __inject(CatalogProductIndex $catalogProductIndexPage) + { + $this->catalogProductIndex = $catalogProductIndexPage; + } + + /** + * Run delete product test. + * + * @param string $products + * @param FixtureFactory $fixtureFactory + * @param Category $category + * @return array + */ + public function test($products, FixtureFactory $fixtureFactory, Category $category) + { + //Steps + $products = explode(',', $products); + $deleteProducts = []; + foreach ($products as &$product) { + list($fixture, $dataset) = explode('::', $product); + $product = $fixtureFactory->createByCode( + $fixture, + [ + 'dataset' => $dataset, + 'data' => [ + 'category_ids' => [ + 'category' => $category, + ], + ] + ] + ); + $product->persist(); + $deleteProducts[] = ['sku' => $product->getSku()]; + } + $this->catalogProductIndex->open(); + $this->catalogProductIndex->getProductGrid()->massaction($deleteProducts, 'Delete', true); + + return ['product' => $products]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..76dd8a27ab1d9b09928e375a29db9d00097f5479 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\DeleteProductEntityTest" summary="Delete products (Simple, Grouped, Configurable)" ticketId="MAGETWO-23272"> + <variation name="DeleteProductEntityTestVariation1"> + <data name="products" xsi:type="string">catalogProductSimple::default</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + <variation name="DeleteProductEntityTestVariation2"> + <data name="products" xsi:type="string">catalogProductVirtual::default</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + <variation name="DeleteProductEntityTestVariation3"> + <data name="products" xsi:type="string">catalogProductSimple::with_one_custom_option</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml index f1b1517bda5240aa13fe8214f40b25f4682470a8..3c3dda5716404d0881365783bb907a9536359bdf 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml @@ -13,7 +13,7 @@ <data name="product/data/price/value" xsi:type="string">99.99</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory</data> <data name="product/data/tier_price/dataset" xsi:type="string">MAGETWO-23002</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> @@ -34,7 +34,7 @@ <data name="product/data/price/value" xsi:type="string">120.00</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/special_price" xsi:type="string">45</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> <data name="product/data/visibility" xsi:type="string">Catalog, Search</data> @@ -54,7 +54,7 @@ <data name="product/data/price/value" xsi:type="string">185.00</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory</data> <data name="product/data/tier_price/dataset" xsi:type="string">MAGETWO-23002</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> @@ -74,7 +74,7 @@ <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> <data name="product/data/visibility" xsi:type="string">Search</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> @@ -90,7 +90,7 @@ <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">5.00</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> <data name="product/data/visibility" xsi:type="string">Catalog</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> @@ -107,9 +107,8 @@ <data name="product/data/price/value" xsi:type="string">145.00</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> <data name="product/data/tier_price/dataset" xsi:type="string">MAGETWO-23002</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> <data name="product/data/visibility" xsi:type="string">Catalog, Search</data> @@ -128,7 +127,7 @@ <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory</data> <data name="product/data/special_price" xsi:type="string">45</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> @@ -147,7 +146,7 @@ <data name="product/data/price/value" xsi:type="string">5.00</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/is_virtual" xsi:type="string">Yes</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> <data name="product/data/visibility" xsi:type="string">Search</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> @@ -164,7 +163,7 @@ <data name="product/data/price/value" xsi:type="string">120.00</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> <data name="product/data/custom_options/dataset" xsi:type="string">options_suite</data> @@ -183,8 +182,7 @@ <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> <data name="product/data/visibility" xsi:type="string">Catalog, Search</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> @@ -201,9 +199,8 @@ <data name="product/data/price/value" xsi:type="string">99.99</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">999</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category_ids/dataset" xsi:type="string">default_subcategory</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_90</data> <data name="product/data/tier_price/dataset" xsi:type="string">MAGETWO-23002</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> <data name="product/data/visibility" xsi:type="string">Catalog</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml index 8eace54087cac573def16b1dec37b03a92c4a759..03cf25cfa8abe5a3886f025398dd4427231b3fb6 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml @@ -36,7 +36,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsRequired" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeSearchableByLabel" /> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertAttributeSearchableByLabel" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeDisplayingOnSearchForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsComparable" /> </variation> @@ -54,12 +54,13 @@ <data name="productAttribute/data/is_comparable" xsi:type="string">No</data> <data name="productAttribute/data/is_visible_on_front" xsi:type="string">Yes</data> <data name="productAttribute/data/used_in_product_listing" xsi:type="string">Yes</data> + <data name="productAttribute/data/is_used_for_promo_rules" xsi:type="string">Yes</data> <data name="productAttribute/data/used_for_sort_by" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsUsedInSortOnFrontend" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsUsedPromoRules" /> + <constraint name="Magento\CatalogRule\Test\Constraint\AssertProductAttributeIsUsedPromoRules" /> </variation> <variation name="CreateProductAttributeEntityTestVariation4"> <data name="productTemplate/dataset" xsi:type="string">custom_attribute_set</data> @@ -99,7 +100,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsComparable" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsFilterable" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsFilterableInSearch" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeSearchableByLabel" /> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertAttributeSearchableByLabel" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeOptionsOnProductForm" /> </variation> <variation name="CreateProductAttributeEntityTestVariation6"> @@ -127,7 +128,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsGlobal" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeDisplayingOnFrontend" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeDisplayingOnSearchForm" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeSearchableByLabel" /> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertAttributeSearchableByLabel" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsComparable" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsUsedInSortOnFrontend" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsFilterable" /> @@ -151,7 +152,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertAddedProductAttributeOnProductForm" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertAttributeSearchableByLabel" /> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertAttributeSearchableByLabel" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeDisplayingOnSearchForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsFilterable" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeIsFilterableInSearch" /> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php index ed6797e5656997827bc7dda19f5dcd1af66874cd..62b27d30a0738c08ee576c1caae308cc01afade3 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Block/Adminhtml/Promo/Catalog/Edit/PromoForm.php @@ -9,16 +9,30 @@ namespace Magento\CatalogRule\Test\Block\Adminhtml\Promo\Catalog\Edit; use Magento\Backend\Test\Block\Widget\FormTabs; use Magento\Mtf\Client\Element\SimpleElement; use Magento\Mtf\Client\Element; +use Magento\Mtf\Client\Locator; use Magento\Mtf\Fixture\FixtureInterface; /** - * Class PromoForm - * Form for creation of a Catalog Price Rule + * Form for creation of a Catalog Price Rule. */ class PromoForm extends FormTabs { /** - * Fill form with tabs + * Add button. + * + * @var string + */ + protected $addButton = '.rule-param-new-child a'; + + /** + * Locator for Customer Segment Conditions. + * + * @var string + */ + protected $conditionFormat = '//*[@id="conditions__1__new_child"]//option[contains(.,"%s")]'; + + /** + * Fill form with tabs. * * @param FixtureInterface $fixture * @param SimpleElement $element @@ -35,7 +49,7 @@ class PromoForm extends FormTabs } /** - * Replace placeholders in each values of data + * Replace placeholders in each values of data. * * @param array $tabs * @param array $replace @@ -57,4 +71,19 @@ class PromoForm extends FormTabs return $tabs; } + + /** + * Check if attribute is available in conditions. + * + * @param string $name + * @return bool + */ + public function isAttributeInConditions($name) + { + $this->_rootElement->find($this->addButton)->click(); + return $this->_rootElement->find( + sprintf($this->conditionFormat, $name), + Locator::SELECTOR_XPATH + )->isVisible(); + } } diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedCatalogPage.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedCatalogPage.php index 7e425f21585dc2f9474fd4593fe8d692093a9fd2..0739420ac1e9c11ac37f71606ffc1eb17cb7537e 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedCatalogPage.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedCatalogPage.php @@ -40,7 +40,10 @@ class AssertCatalogPriceRuleAppliedCatalogPage extends AbstractConstraint '\Magento\Customer\Test\TestStep\LoginCustomerOnFrontendStep', ['customer' => $customer] )->run(); + } else { + $this->objectManager->create('\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep')->run(); } + $cmsIndexPage->open(); foreach ($products as $key => $product) { $categoryName = $product->getCategoryIds()[0]; diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedProductPage.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedProductPage.php index 9b6fe9ffbafbbed143f91319ee8e8e4ae8f0d8a7..119a77cfa0085e81b6bda688e25d1f6421e983c7 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedProductPage.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedProductPage.php @@ -41,12 +41,17 @@ class AssertCatalogPriceRuleAppliedProductPage extends AbstractConstraint '\Magento\Customer\Test\TestStep\LoginCustomerOnFrontendStep', ['customer' => $customer] )->run(); + } else { + $this->objectManager->create('\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep')->run(); } + $cmsIndexPage->open(); foreach ($products as $key => $product) { $categoryName = $product->getCategoryIds()[0]; $cmsIndexPage->getTopmenu()->selectCategoryByName($categoryName); $catalogCategoryViewPage->getListProductBlock()->getProductItem($product)->open(); + + $catalogProductViewPage->getViewBlock()->waitLoader(); $productPriceBlock = $catalogProductViewPage->getViewBlock()->getPriceBlock(); $actualPrice['regular'] = $productPriceBlock->getOldPrice(); $actualPrice['special'] = $productPriceBlock->getSpecialPrice(); diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedShoppingCart.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedShoppingCart.php index e3b15f78366b04ad70e2e0d10c46518d0ae46a50..7a4e4b605ef4b7c64a09c0ac6d613159167002c2 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedShoppingCart.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleAppliedShoppingCart.php @@ -39,7 +39,10 @@ class AssertCatalogPriceRuleAppliedShoppingCart extends AbstractConstraint '\Magento\Customer\Test\TestStep\LoginCustomerOnFrontendStep', ['customer' => $customer] )->run(); + } else { + $this->objectManager->create('\Magento\Customer\Test\TestStep\LogoutCustomerOnFrontendStep')->run(); } + $this->objectManager->create( '\Magento\Checkout\Test\TestStep\AddProductsToTheCartStep', ['products' => $products] diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertProductAttributeIsUsedPromoRules.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertProductAttributeIsUsedPromoRules.php new file mode 100644 index 0000000000000000000000000000000000000000..c0d88eac2804e23a07c77dd111413992d5e1b79b --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertProductAttributeIsUsedPromoRules.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogRule\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Catalog\Test\Fixture\CatalogProductAttribute; +use Magento\CatalogRule\Test\Page\Adminhtml\CatalogRuleNew; +use Magento\CatalogRule\Test\Page\Adminhtml\CatalogRuleIndex; + +/** + * Create a Catalog Price Rules and check whether this attribute visible in Dropdown on Conditions tab. + */ +class AssertProductAttributeIsUsedPromoRules extends AbstractConstraint +{ + /** + * Assert that product attribute can be used on promo rules conditions. + * + * @param CatalogRuleIndex $catalogRuleIndex + * @param CatalogRuleNew $catalogRuleNew + * @param CatalogProductAttribute $attribute + * @return void + */ + public function processAssert( + CatalogRuleIndex $catalogRuleIndex, + CatalogRuleNew $catalogRuleNew, + CatalogProductAttribute $attribute + ) { + $catalogRuleIndex->open(); + $catalogRuleIndex->getGridPageActions()->addNew(); + $catalogRuleNew->getEditForm()->openTab('conditions'); + + \PHPUnit_Framework_Assert::assertTrue( + $catalogRuleNew->getEditForm()->isAttributeInConditions($attribute->getFrontendLabel()), + 'Product attribute can\'t be used on promo rules conditions.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Product attribute can be used on promo rules conditions.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Repository/CatalogRule.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Repository/CatalogRule.xml index ae748f108af441345fe6420cb68940e33583c40c..bbaa64df71ac0bd2e3053abea686df5648c1bb64 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Repository/CatalogRule.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Repository/CatalogRule.xml @@ -17,7 +17,7 @@ <field name="customer_group_ids" xsi:type="array"> <item name="0" xsi:type="string">NOT LOGGED IN</item> </field> - <field name="simple_action" xsi:type="string">By Percentage of the Original Price</field> + <field name="simple_action" xsi:type="string">Apply as percentage of original</field> <field name="discount_amount" xsi:type="string">50</field> </dataset> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.php index 72e040caa781185b32251cf15652ca989a4044de..94868c236dfdda5d5d11352167f334e0672b3403 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.php @@ -37,22 +37,18 @@ class CreateCatalogRuleTest extends AbstractCatalogRuleEntityTest * Create Catalog Price Rule. * * @param CatalogRule $catalogPriceRule - * @param Customer $customer * @param string $product + * @param string $conditionEntity + * @param Customer $customer * @return array */ - public function testCreate(CatalogRule $catalogPriceRule, $product, Customer $customer = null) + public function testCreate(CatalogRule $catalogPriceRule, $product, $conditionEntity, Customer $customer = null) { + /** @var CatalogProductSimple $productSimple */ $productSimple = $this->fixtureFactory->createByCode('catalogProductSimple', ['dataset' => $product]); // Prepare data $catalogPriceRule = $this->applyCustomerGroup($catalogPriceRule, $customer); - $replace = [ - 'conditions' => [ - 'conditions' => [ - '%category_1%' => $productSimple->getDataFieldConfig('category_ids')['source']->getIds()[0], - ], - ], - ]; + $replace = $this->prepareCondition($productSimple, $conditionEntity); // Open Catalog Price Rule page $this->catalogRuleIndex->open(); @@ -107,4 +103,36 @@ class CreateCatalogRuleTest extends AbstractCatalogRuleEntityTest return $catalogPriceRule; } + + /** + * Prepare condition for catalog price rule. + * + * @param CatalogProductSimple $productSimple + * @param string $conditionEntity + * @return array + */ + protected function prepareCondition(CatalogProductSimple $productSimple, $conditionEntity) + { + $result = []; + + switch ($conditionEntity) { + case 'category': + $result['%category_id%'] = $productSimple->getDataFieldConfig('category_ids')['source']->getIds()[0]; + break; + case 'attribute': + /** @var \Magento\Catalog\Test\Fixture\CatalogProductAttribute[] $attrs */ + $attributes = $productSimple->getDataFieldConfig('attribute_set_id')['source'] + ->getAttributeSet()->getDataFieldConfig('assigned_attributes')['source']->getAttributes(); + + $result['%attribute_name%'] = $attributes[0]->getFrontendLabel(); + $result['%attribute_value%'] = $attributes[0]->getOptions()[0]['view']; + break; + } + + return [ + 'conditions' => [ + 'conditions' => $result, + ], + ]; + } } diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.xml b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.xml index 37f84f3977d3f5df44c12963421c15a996d71647..7f7147a08383771b6a5df5a4ab58b7f5ee63d987 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/TestCase/CreateCatalogRuleTest.xml @@ -13,7 +13,8 @@ <data name="catalogPriceRule/data/is_active" xsi:type="string">Active</data> <data name="catalogPriceRule/data/website_ids" xsi:type="string">Main Website</data> <data name="catalogPriceRule/data/customer_group_ids" xsi:type="string">NOT LOGGED IN</data> - <data name="catalogPriceRule/data/conditions" xsi:type="string">[Category|is|%category_1%]</data> + <data name="conditionEntity" xsi:type="string">category</data> + <data name="catalogPriceRule/data/conditions" xsi:type="string">[Category|is|%category_id%]</data> <data name="catalogPriceRule/data/simple_action" xsi:type="string">Adjust final price to this percentage</data> <data name="catalogPriceRule/data/discount_amount" xsi:type="string">90</data> <data name="cartPrice/sub_total" xsi:type="string">90</data> @@ -33,7 +34,8 @@ <data name="catalogPriceRule/data/name" xsi:type="string">rule_name%isolation%</data> <data name="catalogPriceRule/data/is_active" xsi:type="string">Active</data> <data name="catalogPriceRule/data/website_ids" xsi:type="string">Main Website</data> - <data name="catalogPriceRule/data/conditions" xsi:type="string">[Category|is|%category_1%]</data> + <data name="conditionEntity" xsi:type="string">category</data> + <data name="catalogPriceRule/data/conditions" xsi:type="string">[Category|is|%category_id%]</data> <data name="catalogPriceRule/data/simple_action" xsi:type="string">Apply as percentage of original</data> <data name="catalogPriceRule/data/discount_amount" xsi:type="string">50</data> <data name="cartPrice/sub_total" xsi:type="string">5</data> @@ -42,7 +44,7 @@ <data name="productPrice/0/special" xsi:type="string">5</data> <data name="productPrice/0/sub_total" xsi:type="string">5</data> <data name="productPrice/0/regular" xsi:type="string">10</data> - <data name="tag" xsi:type="string">test_type:acceptance_test, stable:no</data> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleInGrid" /> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleNotAppliedCatalogPage" /> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleNotAppliedProductPage" /> @@ -51,5 +53,24 @@ <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedProductPage" /> <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedShoppingCart" /> </variation> + <variation name="CreateCatalogRuleTestVariation3" summary="Create and Apply Catalog Price Rule with Product Attribute in Condition" ticketId="MAGETWO-30095"> + <data name="product" xsi:type="string">product_with_custom_color_attribute</data> + <data name="catalogPriceRule/data/name" xsi:type="string">Catalog Price Rule %isolation%</data> + <data name="catalogPriceRule/data/is_active" xsi:type="string">Active</data> + <data name="catalogPriceRule/data/website_ids" xsi:type="string">Main Website</data> + <data name="catalogPriceRule/data/customer_group_ids/option_0" xsi:type="string">NOT LOGGED IN</data> + <data name="conditionEntity" xsi:type="string">attribute</data> + <data name="catalogPriceRule/data/conditions" xsi:type="string">[%attribute_name%|is|%attribute_value%]</data> + <data name="catalogPriceRule/data/simple_action" xsi:type="string">Apply as percentage of original</data> + <data name="catalogPriceRule/data/discount_amount" xsi:type="string">50</data> + <data name="cartPrice/sub_total" xsi:type="string">280</data> + <data name="cartPrice/grand_total" xsi:type="string">285</data> + <data name="productPrice/0/discount_amount" xsi:type="string">280</data> + <data name="productPrice/0/special" xsi:type="string">280</data> + <data name="productPrice/0/sub_total" xsi:type="string">280</data> + <data name="productPrice/0/regular" xsi:type="string">560</data> + <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedProductPage" /> + <constraint name="Magento\CatalogRule\Test\Constraint\AssertCatalogPriceRuleAppliedShoppingCart" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertAttributeSearchableByLabel.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertAttributeSearchableByLabel.php new file mode 100644 index 0000000000000000000000000000000000000000..52396b06ebe2b0716d764cfa40d05d4b44bc8d0f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertAttributeSearchableByLabel.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Test\Constraint; + +use Magento\Cms\Test\Page\CmsIndex; +use Magento\Mtf\Fixture\InjectableFixture; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\CatalogSearch\Test\Page\CatalogsearchResult; +use Magento\Catalog\Test\Fixture\CatalogProductAttribute; + +/** + * Assert that product attribute is searchable on Frontend. + */ +class AssertAttributeSearchableByLabel extends AbstractConstraint +{ + /** + * Assert that product attribute is searchable on Frontend. + * + * @param CatalogProductAttribute $attribute + * @param CmsIndex $cmsIndex + * @param InjectableFixture $product + * @param CatalogsearchResult $catalogSearchResult + * @return void + */ + public function processAssert( + CatalogProductAttribute $attribute, + CmsIndex $cmsIndex, + InjectableFixture $product, + CatalogsearchResult $catalogSearchResult + ) { + $searchValue = $this->getSearchValue($attribute); + + $cmsIndex->open(); + $cmsIndex->getSearchBlock()->search($searchValue); + + $isVisible = $catalogSearchResult->getListProductBlock()->getProductItem($product)->isVisible(); + while (!$isVisible && $catalogSearchResult->getBottomToolbar()->nextPage()) { + $isVisible = $catalogSearchResult->getListProductBlock()->getProductItem($product)->isVisible(); + } + + \PHPUnit_Framework_Assert::assertTrue($isVisible, 'Product attribute is not searchable on Frontend.'); + } + + /** + * Get search value for product attribute. + * + * @param CatalogProductAttribute $attribute + * @return string + */ + protected function getSearchValue(CatalogProductAttribute $attribute) + { + $searchValue = ''; + + switch ($attribute->getFrontendInput()) { + case 'Multiple Select': + case 'Dropdown': + foreach ($attribute->getOptions() as $option) { + if ($option['is_default'] == 'Yes') { + $searchValue = $option['admin']; + } + } + break; + case 'Text Field': + $searchValue = $attribute->getDefaultValueText(); + break; + case 'Text Area': + $searchValue = $attribute->getDefaultValueTextarea(); + break; + case 'Date': + $searchValue = $attribute->getDefaultValueDate(); + break; + case 'Yes/No': + $searchValue = $attribute->getDefaultValueYesno(); + break; + } + + return $searchValue; + } + + /** + * Returns string representation of object. + * + * @return string + */ + public function toString() + { + return 'Product attribute is searchable on Frontend.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertCatalogSearchNoResult.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertCatalogSearchNoResult.php new file mode 100644 index 0000000000000000000000000000000000000000..ed109f1f790b113d4c940062b1a2434773425ee5 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertCatalogSearchNoResult.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\CatalogSearch\Test\Page\CatalogsearchResult; + +/** + * Assert search has no results. + */ +class AssertCatalogSearchNoResult extends AbstractConstraint +{ + /** + * Assert search has no results and product list in absent. + * + * @param CatalogsearchResult $catalogsearchResult + * @return void + */ + public function processAssert(CatalogsearchResult $catalogsearchResult) + { + \PHPUnit_Framework_Assert::assertFalse( + $catalogsearchResult->getListProductBlock()->isVisible(), + 'Search result has been found.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Search result has not been found.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertCatalogSearchNoResultMessage.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertCatalogSearchNoResultMessage.php new file mode 100644 index 0000000000000000000000000000000000000000..752cb5346dd0fc06cb1a3d3982e676445efa16a2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Constraint/AssertCatalogSearchNoResultMessage.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\CatalogSearch\Test\Page\CatalogsearchResult; + +/** + * Assert that notice message is visible. + */ +class AssertCatalogSearchNoResultMessage extends AbstractConstraint +{ + /** + * Notice message about no results on search. + */ + const NOTICE_MESSAGE = 'Your search returned no results.'; + + /** + * Assert that 'Your search returned no results.' is visible. + * + * @param CatalogsearchResult $catalogSearchResult + * @return void + */ + public function processAssert(CatalogsearchResult $catalogSearchResult) + { + \PHPUnit_Framework_Assert::assertTrue( + $catalogSearchResult->getSearchResultBlock()->isVisibleMessages(self::NOTICE_MESSAGE), + 'Wrong message is displayed or no message at all.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Notice message is visible.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Page/CatalogsearchResult.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Page/CatalogsearchResult.xml index bca2d33440a57f9bae02de8ad2df50ce7351495d..5f594f7acf59b390313ff4519973c7b767549bfd 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Page/CatalogsearchResult.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Page/CatalogsearchResult.xml @@ -6,8 +6,9 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/pages.xsd"> - <page name="CatalogsearchResult" mca="catalogsearch/result" module="Magento_CatalogSearch"> - <block name="listProductBlock" class="Magento\Catalog\Test\Block\Product\ListProduct" locator=".search.results" strategy="css selector"/> - <block name="bottomToolbar" class="Magento\Catalog\Test\Block\Product\ProductList\BottomToolbar" locator=".//*[contains(@class,'toolbar-products')][2]" strategy="xpath"/> - </page> + <page name="CatalogsearchResult" mca="catalogsearch/result" module="Magento_CatalogSearch"> + <block name="listProductBlock" class="Magento\Catalog\Test\Block\Product\ListProduct" locator=".search.results" strategy="css selector" /> + <block name="bottomToolbar" class="Magento\Catalog\Test\Block\Product\ProductList\BottomToolbar" locator=".//*[contains(@class,'toolbar-products')][2]" strategy="xpath" /> + <block name="searchResultBlock" class="Magento\CatalogSearch\Test\Block\Advanced\Result" locator=".column.main" strategy="css selector" /> + </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/AdvancedSearchEntityTest.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/AdvancedSearchEntityTest.php index 740497bffb148aea85ca2c32ffe264e9e33cc4e4..b2773dc7964231e523110427a6afac9a22920e1b 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/AdvancedSearchEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/AdvancedSearchEntityTest.php @@ -78,7 +78,7 @@ class AdvancedSearchEntityTest extends Injectable AdvancedSearch $searchPage ) { $cmsIndex->open(); - $cmsIndex->getSearchBlock()->clickAdvancedSearchButton(); + $cmsIndex->getFooterBlock()->openAdvancedSearch(); $searchForm = $searchPage->getForm(); $searchForm->fill($productSearch); $searchForm->submit(); diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml index 42ffc74b8e4e2ca928c251eb3a294adecc8842d3..7af934c5cff4c3bfda0c58e982e096cc019386ca 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml @@ -47,5 +47,18 @@ <data name="catalogSearch/data/query_text/value" xsi:type="string">bundleProduct::bundle_fixed_product::bundle</data> <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchResult" /> </variation> + <variation name="SearchEntityResultsTestVariation9"> + <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::name</data> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> + </variation> + <variation name="SearchEntityResultsTestVariation10"> + <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::product_with_special_symbols_in_name::name</data> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> + </variation> + <variation name="SearchEntityResultsTestVariation11"> + <data name="catalogSearch/data/query_text/value" xsi:type="string">TryToFindMeAndI'llFindYOU</data> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResultMessage" /> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResult" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/etc/di.xml index 28eeb838b406626fa6ae72e6f94ee9c82df8e0a6..66de584a1e52390abae29f3977358bb2319f5808 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/etc/di.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/etc/di.xml @@ -5,90 +5,96 @@ * See COPYING.txt for license details. */ --> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSuggestSearchingResult"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertAdvancedSearchProductsResult"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchResult"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermForm"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermInGrid"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermOnFrontend"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessSaveMessage"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSynonymOnFrontend"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessDeleteMessage"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermNotInGrid"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermNotOnFrontend"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchSynonymNotOnFrontend"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessMassDeleteMessage"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermMassActionsNotInGrid"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermMassActionNotOnFrontend"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchSynonymMassActionNotOnFrontend"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> - <type name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult"> - <arguments> - <argument name="severity" xsi:type="string">high</argument> - </arguments> - </type> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSuggestSearchingResult"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertAdvancedSearchProductsResult"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchResult"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermForm"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermInGrid"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessSaveMessage"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSynonymOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessDeleteMessage"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermNotInGrid"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermNotOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchSynonymNotOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermSuccessMassDeleteMessage"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermMassActionsNotInGrid"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchTermMassActionNotOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertSearchSynonymMassActionNotOnFrontend"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResult"> + <arguments> + <argument name="severity" xsi:type="string">high</argument> + </arguments> + </type> </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 5d12b4575d9adc26f53cb67398eb84941db42375..22256fed04b100e1cf8f89ccc6f3f6b2ac850194 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 @@ -16,13 +16,13 @@ use Magento\Mtf\Fixture\FixtureInterface; /** * Class Cart - * Shopping cart block + * Shopping Cart block */ class Cart extends Block { // @codingStandardsIgnoreStart /** - * Selector for cart item block + * Locator value for correspondent "Shopping Cart item" block. * * @var string */ @@ -30,42 +30,56 @@ class Cart extends Block // @codingStandardsIgnoreEnd /** - * Proceed to checkout block + * Locator value for "Proceed to One Page Checkout" block. * * @var string */ protected $onepageLinkBlock = '.action.primary.checkout'; /** - * 'Clear Shopping Cart' button + * Locator value for "Clear Shopping Cart" button. * * @var string */ protected $clearShoppingCart = '#empty_cart_button'; /** - * 'Update Shopping Cart' button + * Locator value for "Update Shopping Cart" button. * * @var string */ protected $updateShoppingCart = '.update[name="update_cart_action"]'; /** - * Cart empty block selector + * Locator value for "Check out with PayPal" button. + * + * @var string + */ + protected $paypalCheckoutButton = '[data-action=checkout-form-submit]'; + + /** + * Locator value for "empty Shopping Cart" block. * * @var string */ protected $cartEmpty = '.cart-empty'; /** - * Cart container selector + * Locator value for "Shopping Cart" container. * * @var string */ protected $cartContainer = '.cart-container'; /** - * Get cart item block + * Locator value for "Remove Product" button. + * + * @var string + */ + protected $deleteItemButton = '.action.action-delete'; + + /** + * Get Shopping Cart item. * * @param FixtureInterface $product * @return CartItem @@ -93,7 +107,7 @@ class Cart extends Block } /** - * Get proceed to checkout block + * Get "Proceed to One Page Checkout" block. * * @return Link */ @@ -105,17 +119,17 @@ class Cart extends Block } /** - * Press 'Check out with PayPal' button + * Click "Check out with PayPal" button. * * @return void */ public function paypalCheckout() { - $this->_rootElement->find('[data-action=checkout-form-submit]', Locator::SELECTOR_CSS)->click(); + $this->_rootElement->find($this->paypalCheckoutButton)->click(); } /** - * Returns the total discount price + * Get total discount Price value. * * @return string * @throws Exception @@ -135,20 +149,19 @@ class Cart extends Block } /** - * Clear shopping cart + * Clear Shopping Cart. * * @return void */ public function clearShoppingCart() { - $clearShoppingCart = $this->_rootElement->find($this->clearShoppingCart); - if ($clearShoppingCart->isVisible()) { - $clearShoppingCart->click(); + while (!$this->cartIsEmpty()) { + $this->_rootElement->find($this->deleteItemButton)->click(); } } /** - * Check if a product has been successfully added to the cart + * Check if Product is present in Shopping Cart or not. * * @param FixtureInterface $product * @return boolean @@ -159,27 +172,29 @@ class Cart extends Block } /** - * Update shopping cart + * Update Shopping Cart. * * @return void */ public function updateShoppingCart() { - $this->_rootElement->find($this->updateShoppingCart, Locator::SELECTOR_CSS)->click(); + $this->_rootElement->find($this->updateShoppingCart)->click(); } /** - * Check that cart is empty + * Check if Shopping Cart is empty or not. * * @return bool */ public function cartIsEmpty() { - return $this->_rootElement->find($this->cartEmpty, Locator::SELECTOR_CSS)->isVisible(); + return $this->_rootElement->find($this->cartEmpty)->isVisible(); } /** - * Wait while cart container is loaded + * Wait while Shopping Cart container is loaded. + * + * @return void */ public function waitCartContainerLoading() { diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php index f0d771fff09c7378b78e9e647f1313c4e22ffb5c..04f4d4569f94a3ab3b44d6f0015d6c92d9848167 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Sidebar.php @@ -58,18 +58,11 @@ class Sidebar extends Block protected $counterQty = '.minicart-wrapper .counter.qty'; /** - * Count product in cart block. + * Locator value for Mini Shopping Cart wrapper. * * @var string */ - protected $counterNumberBlock = '//*[@class="counter-number" and normalize-space(text()) != ""]'; - - /** - * Count product in cart wrapper. - * - * @var string - */ - protected $counterNumberWrapper = '/ancestor::*[@class="minicart-wrapper"]'; + protected $counterNumberWrapper = '.minicart-wrapper'; /** * Loading masc. @@ -157,10 +150,10 @@ class Sidebar extends Block public function waitInit() { $browser = $this->browser; - $selector = $this->counterNumberBlock . $this->counterNumberWrapper; + $selector = $this->counterNumberWrapper; $browser->waitUntil( function () use ($browser, $selector) { - $counterQty = $browser->find($selector, Locator::SELECTOR_XPATH); + $counterQty = $browser->find($selector); return $counterQty->isVisible() ? true : null; } ); 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 5229432c3d997f112086ee34c1f24fc13d2bb6eb..663a88d34b66cc410e3106a693340fdb4e1af367 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 @@ -48,14 +48,14 @@ class Totals extends Block * * @var string */ - protected $subtotalExclTax = '.totals.sub.excl span'; + protected $subtotalExclTax = '.totals.sub.excl .price'; /** * Subtotal search mask * * @var string */ - protected $subtotalInclTax = '.totals.sub.incl span'; + protected $subtotalInclTax = '.totals.sub.incl .price'; /** * Tax search mask diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Page/CmsIndex.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/Page/CmsIndex.xml new file mode 100644 index 0000000000000000000000000000000000000000..05d380b450464ebe3f1d888c9f1c7d00d1331d06 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Page/CmsIndex.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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="CmsIndex" mca="cms/index/index"> + <block name="cartSidebarBlock" class="Magento\Checkout\Test\Block\Cart\Sidebar" locator="[data-block='minicart']" strategy="css selector" /> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml index 9e388b13f7aade6faf292d3dbd5ef0f26ca789f0..69ab253de55c0f0b4b797b3eaf5c61488a57cf4d 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/AddProductsToShoppingCartEntityTest.xml @@ -45,7 +45,7 @@ </variation> <variation name="AddProductsToShoppingCartEntityTestVariation5"> <data name="productsData" xsi:type="string">configurableProduct::default</data> - <data name="cart/data/grand_total" xsi:type="string">360</data> + <data name="cart/data/grand_total" xsi:type="string">120</data> <constraint name="Magento\Checkout\Test\Constraint\AssertPriceInShoppingCart" /> <constraint name="Magento\Checkout\Test\Constraint\AssertProductQtyInShoppingCart" /> <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInShoppingCart" /> @@ -72,7 +72,7 @@ </variation> <variation name="AddProductsToShoppingCartEntityTestVariation8"> <data name="productsData" xsi:type="string">catalogProductSimple::with_two_custom_option, catalogProductVirtual::product_50_dollar, downloadableProduct::with_two_separately_links, groupedProduct::three_simple_products, configurableProduct::default, bundleProduct::bundle_dynamic_product, bundleProduct::bundle_dynamic_product</data> - <data name="cart/data/grand_total" xsi:type="string">3092.43</data> + <data name="cart/data/grand_total" xsi:type="string">2852.43</data> <constraint name="Magento\Checkout\Test\Constraint\AssertPriceInShoppingCart" /> <constraint name="Magento\Checkout\Test\Constraint\AssertProductQtyInShoppingCart" /> <constraint name="Magento\Checkout\Test\Constraint\AssertSubtotalInShoppingCart" /> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6d9516684512359904181b06d7899f8af8f7021e --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Checkout\Test\TestCase; + +use Magento\Mtf\TestCase\Scenario; + +/** + * Preconditions: + * 1. Configure shipping method. + * 2. Configure payment method. + * 3. Create products. + * 4. Create and setup customer. + * 5. Create sales rule according to dataset. + * + * Steps: + * 1. Go to Frontend. + * 2. Add products to the cart. + * 3. Apply discounts in shopping cart according to dataset. + * 4. In 'Estimate Shipping and Tax' section specify destination using values from Test Data + * 5. Click the 'Get a Quote' button + * 6. In the section appeared select Shipping method, click the 'Update Total' button + * 7. Click the 'Proceed to Checkout' button. + * 8. Select checkout method according to dataset. + * 9. Fill billing information and select the 'Ship to this address' option. + * 10. Select shipping method. + * 11. Select payment method (use reward points and store credit if available). + * 12. Verify order total on review step. + * 13. Place order. + * 14. Perform assertions. + * + * @group One_Page_Checkout_(CS) + * @ZephyrId MAGETWO-27485, MAGETWO-12412, MAGETWO-12429 + * @ZephyrId MAGETWO-12444, MAGETWO-12848, MAGETWO-12849, MAGETWO-12850 + */ +class OnePageCheckoutTest extends Scenario +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'CS'; + const TEST_TYPE = 'acceptance_test, 3rd_party_test'; + const TO_MAINTAIN = 'yes'; + /* end tags */ + + /** + * Runs one page checkout test. + * + * @return void + */ + public function test() + { + $this->executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml index 582997ee52ca18bddcc064bfb632be6395d4695d..696bf1324beea2c303bda0496702588f2b70be3a 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml @@ -7,6 +7,101 @@ --> <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="OnePageCheckout within Offline Payment Methods" ticketId="MAGETWO-27485"> - <variation name="OnePageCheckoutTestVariation1" /> + <variation name="OnePageCheckoutTestVariation1" summary="Checkout as UK guest with virtual product using coupon for not logged in customers"> + <data name="products" xsi:type="string">catalogProductVirtual::default</data> + <data name="salesRule" xsi:type="string">active_sales_rule_with_percent_price_discount_coupon</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="billingAddress/dataset" xsi:type="string">UK_address</data> + <data name="checkoutMethod" xsi:type="string">guest</data> + <data name="prices" xsi:type="array"> + <item name="grandTotal" xsi:type="string">5.00</item> + </data> + <data name="payment/method" xsi:type="string">checkmo</data> + <data name="status" xsi:type="string">Pending</data> + <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Notification, Hold, Invoice, Edit</data> + <data name="configData" xsi:type="string">checkmo_specificcountry_gb</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> + </variation> + <variation name="OnePageCheckoutTestVariation2" summary="US customer during checkout using coupon for all customer groups"> + <data name="products" xsi:type="string">catalogProductSimple::default</data> + <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="billingAddress/dataset" xsi:type="string">US_address_1</data> + <data name="checkoutMethod" xsi:type="string">guest</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">285.00</item> + </data> + <data name="payment/method" xsi:type="string">banktransfer</data> + <data name="status" xsi:type="string">Pending</data> + <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Notification, Hold, Reorder, Invoice, Edit</data> + <data name="configData" xsi:type="string">banktransfer</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> + </variation> + <variation name="OnePageCheckoutTestVariation3" summary="Checkout as UK guest with virtual product"> + <data name="products" xsi:type="string">catalogProductVirtual::default</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="billingAddress/dataset" xsi:type="string">UK_address</data> + <data name="checkoutMethod" xsi:type="string">guest</data> + <data name="prices" xsi:type="array"> + <item name="grandTotal" xsi:type="string">10.00</item> + </data> + <data name="payment/method" xsi:type="string">banktransfer</data> + <data name="status" xsi:type="string">Pending</data> + <data name="orderButtonsAvailable" xsi:type="string">Back, Cancel, Send Notification, Hold, Invoice, Edit</data> + <data name="configData" xsi:type="string">banktransfer_specificcountry_gb</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> + </variation> + <variation name="OnePageCheckoutTestVariation4" summary="One Page Checkout Products with Special Prices" ticketId="MAGETWO-12429"> + <data name="products" xsi:type="string">catalogProductSimple::product_with_special_price, configurableProduct::product_with_special_price</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data> + <data name="checkoutMethod" xsi:type="string">login</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">20.00</item> + </data> + <data name="cart/data/subtotal" xsi:type="string">10.00</data> + <data name="cart/data/shipping_amount" xsi:type="string">10.00</data> + <data name="cart/data/grand_total" xsi:type="string">20.00</data> + <data name="billingCheckboxState" xsi:type="string">Yes</data> + <data name="payment/method" xsi:type="string">checkmo</data> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="configData" xsi:type="string">checkmo</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal"/> + </variation> + <variation name="OnePageCheckoutTestVariation5" summary="Guest Checkout using Check/Money Order and Free Shipping with Prices/Taxes Verifications" ticketId="MAGETWO-12412"> + <data name="products" xsi:type="string">catalogProductSimple::product_10_dollar, configurableProduct::with_one_option, bundleProduct::bundle_fixed_100_dollar_product</data> + <data name="taxRule" xsi:type="string">us_ca_ny_rule</data> + <data name="shippingAddress/dataset" xsi:type="string">US_address_1</data> + <data name="checkoutMethod" xsi:type="string">guest</data> + <data name="shipping/shipping_service" xsi:type="string">Free Shipping</data> + <data name="shipping/shipping_method" xsi:type="string">Free</data> + <data name="prices" xsi:type="array"> + <item name="grandTotal" xsi:type="string">130.98</item> + </data> + <data name="cart/data/subtotal" xsi:type="string">121.00</data> + <data name="cart/data/tax_amount" xsi:type="string">9.98</data> + <data name="cart/data/shipping_amount" xsi:type="string">0.00</data> + <data name="cart/data/grand_total" xsi:type="string">130.98</data> + <data name="billingCheckboxState" xsi:type="string">Yes</data> + <data name="payment/method" xsi:type="string">checkmo</data> + <data name="configData" xsi:type="string">checkmo, freeshipping_minimum_order_amount_100</data> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal"/> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml index 06d7a080176c96e2389c3fdd308e20c03da1299b..95eb13b50a2f9b87ceacd0028cfddce00faeaf4c 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml @@ -41,5 +41,12 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertProductQtyInShoppingCart" /> <constraint name="Magento\Checkout\Test\Constraint\AssertProductOptionsAbsentInShoppingCart" /> </variation> + <variation name="UpdateProductFromMiniShoppingCartEntityTestVariation5"> + <data name="description" xsi:type="string">Update Virtual product in mini shopping cart</data> + <data name="originalProduct" xsi:type="string">catalogProductVirtual::default</data> + <data name="checkoutData/dataset" xsi:type="string">virtual_update_mini_shopping_cart</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertProductQtyInMiniShoppingCart" /> + <constraint name="Magento\Checkout\Test\Constraint\AssertProductQtyInShoppingCart" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.php index 39951aa818cde6da5d54b8735e6a16bf044357a7..57963c6e28ce6e3696e0472b1e945d42a2b67bc3 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.php @@ -102,6 +102,7 @@ class UpdateShoppingCartTest extends Injectable { // Preconditions $product->persist(); + $this->checkoutCart->open(); $this->checkoutCart->getCartBlock()->clearShoppingCart(); // Steps diff --git a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.php b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.php index 2025417f5da0940fa84abf249f9e233d87d4e4da..ee6191a0a7d8d7802d00c786e381c66383efe0a7 100644 --- a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.php @@ -20,7 +20,7 @@ use Magento\Mtf\TestCase\Scenario; * 5. Perform all assertions * * @group Terms_and_Conditions_(CS) - * @ZephyrId MAGETWO-29586 + * @ZephyrId MAGETWO-29586, MAGETWO-32499 */ class CreateTermEntityTest extends Scenario { diff --git a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.xml index 843e26dc329231d699c346536f8452367ea49616..7471bf899ef868e8ea3ea100fcd90f33834c00cf 100644 --- a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/TestCase/CreateTermEntityTest.xml @@ -79,8 +79,7 @@ <constraint name="Magento\CheckoutAgreements\Test\Constraint\AssertTermInGrid" /> <constraint name="Magento\CheckoutAgreements\Test\Constraint\AssertTermAbsentOnCheckout" /> </variation> - <variation name="CreateTermEntityTestVariation5"> - <data name="description" xsi:type="string">Create enabled term, check with multishipping</data> + <variation name="CreateTermEntityTestVariation5" summary="Terms and conditions on multishipping" ticketId="MAGETWO-32499"> <data name="configData" xsi:type="string">checkout_term_condition</data> <data name="products" xsi:type="string">catalogProductSimple::default, catalogProductSimple::default</data> <data name="agreement/data/name" xsi:type="string">name%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsIndex.xml index f6d62c1f0ffc7d482cd135ad9c94b095ba407d30..105673dceb26d659cad816f2ec7f9764a0ae43fd 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsIndex.xml @@ -13,8 +13,6 @@ <block name="footerBlock" class="Magento\Theme\Test\Block\Html\Footer" locator="footer.page-footer" strategy="css selector" /> <block name="linksBlock" class="Magento\Theme\Test\Block\Links" locator=".header .links" strategy="css selector" /> <block name="storeSwitcherBlock" class="Magento\Store\Test\Block\Switcher" locator="[data-ui-id='language-switcher']" strategy="css selector" /> - <block name="cartSidebarBlock" class="Magento\Checkout\Test\Block\Cart\Sidebar" locator="[data-block='minicart']" strategy="css selector" /> - <block name="compareProductsBlock" class="Magento\Catalog\Test\Block\Product\Compare\Sidebar" locator=".sidebar.sidebar-additional" strategy="css selector" /> <block name="currencyBlock" class="Magento\Directory\Test\Block\Currency\Switcher" locator=".switcher.currency" strategy="css selector" /> <block name="cmsPageBlock" class="Magento\Cms\Test\Block\Page" locator=".page-main" strategy="css selector" /> <block name="widgetView" class="Magento\Widget\Test\Block\WidgetView" locator=".widget" strategy="css selector" /> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/AffectedAttributeSet.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/AffectedAttributeSet.php index c838565a3d1ade69fe36b7cecf324a8bcd7e5402..eac63680dc2de847e073db5114852769058c0514 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/AffectedAttributeSet.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/AffectedAttributeSet.php @@ -23,14 +23,14 @@ class AffectedAttributeSet extends ParentForm * * @var string */ - protected $confirmButton = '//button[contains(@id,"confirm-button")]'; + protected $confirmButton = '[data-role=action]'; /** * Locator buttons new name attribute set * * @var string */ - protected $affectedAttributeSetNew = '#affected-attribute-set-new'; + protected $affectedAttributeSetNew = '#new-affected-attribute-set'; /** * Fill popup form @@ -61,6 +61,6 @@ class AffectedAttributeSet extends ParentForm */ public function confirm() { - $this->_rootElement->find($this->confirmButton, Locator::SELECTOR_XPATH)->click(); + $this->_rootElement->find($this->confirmButton, Locator::SELECTOR_CSS)->click(); } } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Tab/Super/Config.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Tab/Super/Config.php index 75aff573bcf4926f824e2dc2fdd05fca60f790af..846c920cd94396e9334c550295e5506257157133 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Tab/Super/Config.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Tab/Super/Config.php @@ -17,15 +17,15 @@ use Magento\Mtf\Client\Locator; */ class Config extends Tab { + /** @var string */ + protected $createConfigurationsButton = '[data-action=open-steps-wizard]'; + /** * Selector for trigger show/hide "Variations" tab. * * @var string */ - protected $variationsTabTrigger = '[data-target="#super_config-content"][data-toggle="collapse"] span'; - - /** @var string */ - protected $createConfigurationsButton = '[data-action=open-steps-wizard]'; + protected $variationsTabTrigger = '[data-tab=super_config] [data-role=trigger]'; /** * Selector for content "Variations" tab. @@ -55,13 +55,6 @@ class Config extends Tab */ protected $template = './ancestor::body'; - /** - * Selector for variations tab wrapper. - * - * @var string - */ - protected $variationsTabWrapper = '#super_config-wrapper'; - /** * Attribute element selector. * @@ -124,7 +117,6 @@ class Config extends Tab { $content = $this->_rootElement->find($this->variationsTabContent); if (!$content->isVisible()) { - $this->_rootElement->find($this->variationsTabWrapper)->click(); $this->_rootElement->find($this->variationsTabTrigger)->click(); $this->waitForElementVisible($this->variationsTabContent); } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/FormPageActions.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/FormPageActions.php index b1822f54f0eb8d131c2cca45748f240a9b0c026e..d52cbe77d49fdb73ac9eb6ec900cb4d1e8d16285 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/FormPageActions.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/FormPageActions.php @@ -15,12 +15,14 @@ use Magento\Mtf\Fixture\FixtureInterface; */ class FormPageActions extends \Magento\Catalog\Test\Block\Adminhtml\Product\FormPageActions { + // @codingStandardsIgnoreStart /** - * Selector for "Affected Attribute Set" popup form + * Selector for "Affected Attribute Set" modal window * * @var string */ - protected $affectedAttributeSetForm = '//ancestor::body//div[div[@id="affected-attribute-set-form"]]'; + protected $affectedAttributeSetForm = '//div[@data-role="affected-attribute-set-selector"]/ancestor::*[@data-role="modal"]'; + // @codingStandardsIgnoreEnd /** * Click on "Save" button @@ -46,7 +48,7 @@ class FormPageActions extends \Magento\Catalog\Test\Block\Adminhtml\Product\Form { return $this->blockFactory->create( '\Magento\ConfigurableProduct\Test\Block\Adminhtml\Product\AffectedAttributeSet', - ['element' => $this->_rootElement->find($this->affectedAttributeSetForm, Locator::SELECTOR_XPATH)] + ['element' => $this->browser->find($this->affectedAttributeSetForm, Locator::SELECTOR_XPATH)] ); } } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertChildProductsInGrid.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertChildProductsInGrid.php index 557e1c09882feee7e9194e88305861f2676b9e29..923e439e0394017ca49bb428ae9b6597799a1304 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertChildProductsInGrid.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertChildProductsInGrid.php @@ -31,7 +31,7 @@ class AssertChildProductsInGrid extends AbstractConstraint public function processAssert(CatalogProductIndex $productGrid, ConfigurableProduct $product) { $configurableAttributesData = $product->getConfigurableAttributesData(); - $productType = $product->getIsVirtual() === "Yes" ? 'Virtual Product' : 'Simple Product'; + $productType = $product->getProductHasWeight() === "Yes" ? 'Simple Product' : 'Virtual Product'; $errors = []; $productGrid->open(); diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductForm.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductForm.php index eb733915bc22e09ba2cd246f56f5dcccc2f2e4e2..dbe514e6e8545bcd06e913c583292dc54ea62e82 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductForm.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductForm.php @@ -90,7 +90,7 @@ class AssertConfigurableProductForm extends AssertProductForm protected function prepareFormData(array $data, array $sortFields = []) { // filter values and reset keys in variation matrix - $variationsMatrix = $data['configurable_attributes_data']['matrix']; + $variationsMatrix = $this->trimCurrencyForPriceInMatrix($data['configurable_attributes_data']['matrix']); foreach ($variationsMatrix as $key => $variationMatrix) { $variationsMatrix[$key] = array_diff_key($variationMatrix, array_flip($this->skippedVariationMatrixFields)); } @@ -101,4 +101,21 @@ class AssertConfigurableProductForm extends AssertProductForm } return $data; } + + /** + * Escape currency for price in matrix + * + * @param array $variationsMatrix + * @param string $currency + * @return array + */ + protected function trimCurrencyForPriceInMatrix($variationsMatrix, $currency = '$') + { + foreach ($variationsMatrix as &$variation) { + if (isset($variation['price'])) { + $variation['price'] = str_replace($currency, '', $variation['price']); + } + } + return $variationsMatrix; + } } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Fixture/ConfigurableProduct.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Fixture/ConfigurableProduct.xml index 96623e3492dae619060e60b2594c78b9d6b3c525..f355522a64886ef349c76a381127b6c617fe85da 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Fixture/ConfigurableProduct.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Fixture/ConfigurableProduct.xml @@ -36,7 +36,6 @@ <field name="enable_googlecheckout" is_required="0" /> <field name="gallery" is_required="0" /> <field name="gift_message_available" is_required="0" /> - <field name="group_price" is_required="0" group="advanced-pricing" repository="Magento\Catalog\Test\Repository\Product\GroupPriceOptions" /> <field name="has_options" is_required="0" /> <field name="image" is_required="0" /> <field name="image_label" is_required="0" /> @@ -74,7 +73,7 @@ <field name="url_path" is_required="0" /> <field name="visibility" is_required="0" group="autosettings" /> <field name="weight" is_required="0" group="product-details" /> - <field name="is_virtual" group="product-details" /> + <field name="product_has_weight" group="product-details" /> <field name="id" /> <field name="type_id" group="null" /> <field name="attribute_set_id" group="product-details" source="Magento\Catalog\Test\Fixture\Product\AttributeSetId" /> 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 fbffeb8f0702851117a1ac24e24c502ec4f8cc41..2fd3b20f3769c10a79c4b97290b34470f712cc82 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 @@ -14,6 +14,7 @@ <item name="value" xsi:type="string">120</item> </field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -45,6 +46,7 @@ </field> <field name="special_price" xsi:type="string">9</field> <field name="weight" xsi:type="string">5</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -75,6 +77,7 @@ <item name="value" xsi:type="string">120</item> </field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -105,6 +108,7 @@ <item name="value" xsi:type="string">120</item> </field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -135,6 +139,7 @@ <item name="value" xsi:type="string">120</item> </field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -161,8 +166,8 @@ <field name="price" xsi:type="array"> <item name="value" xsi:type="string">120</item> </field> - <field name="is_virtual" xsi:type="string">No</field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -193,6 +198,7 @@ <item name="value" xsi:type="string">10</item> </field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -223,6 +229,7 @@ <item name="value" xsi:type="string">120</item> </field> <field name="weight" xsi:type="string">30</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -254,6 +261,7 @@ <item name="dataset" xsi:type="string">taxable_goods</item> </field> <field name="weight" xsi:type="string">1</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1</item> <item name="is_in_stock" xsi:type="string">In Stock</item> @@ -286,6 +294,7 @@ <item name="dataset" xsi:type="string">taxable_goods</item> </field> <field name="weight" xsi:type="string">1</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -307,6 +316,7 @@ <item name="value" xsi:type="string">10</item> </field> <field name="weight" xsi:type="string">1</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="tax_class_id" xsi:type="array"> @@ -339,6 +349,7 @@ <item name="dataset" xsi:type="string">taxable_goods</item> </field> <field name="weight" xsi:type="string">1</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1</item> <item name="is_in_stock" xsi:type="string">In Stock</item> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/CheckoutData.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/CheckoutData.xml index 2579101e6dfd3a790e54d407ac965309a7436956..4f058ad0611c0f5eff12d181acb7607d920361a5 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/CheckoutData.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/CheckoutData.xml @@ -22,9 +22,9 @@ </field> <field name="qty" xsi:type="string">3</field> <field name="cartItem" xsi:type="array"> - <item name="price" xsi:type="string">120</item> + <item name="price" xsi:type="string">40</item> <item name="qty" xsi:type="string">3</item> - <item name="subtotal" xsi:type="string">360</item> + <item name="subtotal" xsi:type="string">120</item> </field> </dataset> @@ -142,9 +142,9 @@ </field> <field name="qty" xsi:type="string">1</field> <field name="cartItem" xsi:type="array"> - <item name="price" xsi:type="string">9</item> + <item name="price" xsi:type="string">1</item> <item name="qty" xsi:type="string">1</item> - <item name="subtotal" xsi:type="string">9</item> + <item name="subtotal" xsi:type="string">1</item> </field> </dataset> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e3dfb35aa29c68699d8b58319b2f38fa76205623 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\DeleteProductEntityTest"> + <variation name="DeleteProductEntityTestVariation9"> + <data name="products" xsi:type="string">configurableProduct::default</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + <variation name="DeleteProductEntityTestVariation10"> + <data name="products" xsi:type="string">configurableProduct::with_one_option</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..fc24de8c1c44157d3cd4a1ffb95e49dbad2f9099 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\Checkout\Test\TestCase\DeleteProductFromMiniShoppingCartTest" summary="Delete Configurable Product from Mini Shopping Cart" ticketId="MAGETWO-29104"> + <variation name="DeleteConfigurableProductFromMiniShoppingCartTestVariation"> + <data name="products" xsi:type="string">configurableProduct::default</data> + <data name="deletedProductIndex" xsi:type="string">0</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/TaxCalculationTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/TaxCalculationTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..c433a8570daad3f649d17d72cb67a684639f7bed --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/TaxCalculationTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\Tax\Test\TestCase\TaxCalculationTest"> + <variation name="TaxCalculationTestConfigurableProduct" ticketId="MAGETWO-42708"> + <data name="product" xsi:type="string">configurableProduct::two_options_by_one_dollar</data> + <data name="taxRule" xsi:type="string">us_full_tax_rule</data> + <data name="shippingAddress/dataset" xsi:type="string">US_address_1</data> + <data name="customer/dataset" xsi:type="string">johndoe_unique</data> + <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> + <data name="catalogRule" xsi:type="string">-</data> + <data name="configData" xsi:type="string">display_excluding_including_tax</data> + <data name="qty" xsi:type="string">3</data> + <data name="prices/category_price_excl_tax" xsi:type="string">1.00</data> + <data name="prices/category_price_incl_tax" xsi:type="string">1.10</data> + <data name="prices/product_view_price_excl_tax" xsi:type="string">1.00</data> + <data name="prices/product_view_price_incl_tax" xsi:type="string">1.10</data> + <data name="prices/cart_item_price_excl_tax" xsi:type="string">1.00</data> + <data name="prices/cart_item_price_incl_tax" xsi:type="string">1.10</data> + <data name="prices/cart_item_subtotal_excl_tax" xsi:type="string">3.00</data> + <data name="prices/cart_item_subtotal_incl_tax" xsi:type="string">3.30</data> + <data name="prices/subtotal_excl_tax" xsi:type="string">3.00</data> + <data name="prices/subtotal_incl_tax" xsi:type="string">3.30</data> + <data name="prices/shipping_excl_tax" xsi:type="string">15.00</data> + <data name="prices/shipping_incl_tax" xsi:type="string">15.00</data> + <data name="prices/discount" xsi:type="string">1.50</data> + <data name="prices/tax" xsi:type="string">0.3</data> + <data name="prices/grand_total_excl_tax" xsi:type="string">16.50</data> + <data name="prices/grand_total_incl_tax" xsi:type="string">16.80</data> + <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesExcludingIncludingTax" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Handler/Customer/Webapi.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Handler/Customer/Webapi.php new file mode 100644 index 0000000000000000000000000000000000000000..1934bc456d92061105b2faae79919f69de35293f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Handler/Customer/Webapi.php @@ -0,0 +1,180 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Handler\Customer; + +use Magento\Customer\Test\Fixture\Customer; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\Handler\Webapi as AbstractWebapi; + +/** + * Webapi handler for creating customer. + */ +class Webapi extends AbstractWebapi implements CustomerInterface +{ + /** + * Default customer group. + */ + const GENERAL_GROUP = '1'; + + /** + * Mapping values for data. + * + * @var array + */ + protected $mappingData = [ + 'country_id' => [ + 'United States' => 'US', + 'United Kingdom' => 'GB' + ], + 'region_id' => [ + 'California' => 12, + 'New York' => 43, + 'Texas' => 57, + ], + ]; + + /** + * Create customer via Web API. + * + * @param FixtureInterface|null $customer + * @return array + * @throws \Exception + */ + public function persist(FixtureInterface $customer = null) + { + /** @var Customer $customer */ + $data = $this->prepareData($customer); + $url = $_ENV['app_frontend_url'] . 'rest/V1/customers'; + + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + + if (empty($response['id'])) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Customer creation by Web API handler was not successful!'); + } + + return ['id' => $response['id']]; + } + + /** + * Prepare customer data for Web API. + * + * @param Customer $customer + * @return array + */ + protected function prepareData(Customer $customer) + { + $data['customer'] = $customer->getData(); + $data['customer']['group_id'] = $this->getCustomerGroup($customer); + $data['password'] = $data['customer']['password']; + unset($data['customer']['password']); + unset($data['customer']['password_confirmation']); + $data = $this->prepareAddressData($data); + + return $data; + } + + /** + * Get customer group. + * + * @param Customer $customer + * @return string + */ + protected function getCustomerGroup(Customer $customer) + { + return $customer->hasData('group_id') + ? $customer->getDataFieldConfig('group_id')['source']->getCustomerGroup()->getCustomerGroupId() + : self::GENERAL_GROUP; + } + + /** + * Prepare address data for Web API. + * + * @param array $data + * @return array + */ + protected function prepareAddressData(array $data) + { + if (!isset($data['customer']['address'])) { + return $data; + } + foreach ($data['customer']['address'] as $key => $addressData) { + $addressData['country_id'] = $this->mappingData['country_id'][$addressData['country_id']]; + $addressData = $this->prepareRegionData($addressData); + $addressData = $this->prepareStreetData($addressData); + $addressData = $this->prepareDefaultAddressData($addressData); + unset($addressData['email']); + $data['customer']['addresses'][$key] = $addressData; + } + unset($data['customer']['address']); + + return $data; + } + + /** + * Prepare region data for the address. + * + * @param array $addressData + * @return array + */ + protected function prepareRegionData(array $addressData) + { + if (isset($addressData['region'])) { + $addressData['region'] = [ + 'region' => $addressData['region'], + ]; + } + if (isset($addressData['region_id'])) { + $addressData['region'] = [ + 'region_id' => $this->mappingData['region_id'][$addressData['region_id']] + ]; + unset($addressData['region_id']); + } + + return $addressData; + } + + /** + * Prepare street data for the address. + * + * @param array $addressData + * @return array + */ + protected function prepareStreetData(array $addressData) + { + if (!is_array($addressData['street'])) { + $street[] = $addressData['street']; + $addressData['street'] = $street; + } + + return $addressData; + } + + /** + * Prepare default address data. + * + * @param array $addressData + * @return array + */ + protected function prepareDefaultAddressData(array $addressData) + { + if (isset($addressData['default_billing']) && $addressData['default_billing'] === 'Yes') { + $addressData['default_billing'] = true; + } else { + $addressData['default_billing'] = false; + } + if (isset($addressData['default_shipping']) && $addressData['default_shipping'] === 'Yes') { + $addressData['default_shipping'] = true; + } else { + $addressData['default_shipping'] = false; + } + + return $addressData; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Customer.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Customer.xml index 15df2b81f2518ed677cb21e5a3b9a3692808c093..ca4a46913b3b86ccc06462b957e1a7b23bdefded 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Customer.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Repository/Customer.xml @@ -118,6 +118,14 @@ </field> </dataset> + <dataset name="register_customer"> + <field name="firstname" xsi:type="string">John</field> + <field name="lastname" xsi:type="string">Doe</field> + <field name="email" xsi:type="string">JohnDoe_%isolation%@example.com</field> + <field name="password" xsi:type="string">123123q</field> + <field name="password_confirmation" xsi:type="string">123123q</field> + </dataset> + <dataset name="backend_customer"> <field name="firstname" xsi:type="string">John</field> <field name="lastname" xsi:type="string">Doe%isolation%</field> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/GridFilteringTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/GridFilteringTest.xml index 6c34dc0371fc4f64f4e88ddaa3343de93c18b63e..ebc2492058a5d8b79955b24be3fe7cab5268da46 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/GridFilteringTest.xml +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/GridFilteringTest.xml @@ -15,7 +15,7 @@ <data name="filters" xsi:type="array"> <item name="0" xsi:type="array"> <item name="name" xsi:type="string">:lastname</item> - <item name="entity_id_from" xsi:type="string">:id</item> + <item name="email" xsi:type="string">:email</item> </item> <item name="1" xsi:type="array"> <item name="email" xsi:type="string">:email</item> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.php b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.php index fe690d7c18ca71cc0b370ae05952bbb7ae71a341..8d89091816aa5a217f385b50ce31bbd0fb205ac7 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.php @@ -63,13 +63,13 @@ class RegisterCustomerFrontendEntityTest extends Injectable } /** - * Create Customer account on frontend + * Create Customer account on Storefront. * * @param Customer $customer */ public function test(Customer $customer) { - //Steps + // Steps $this->cmsIndex->open(); $this->cmsIndex->getLinksBlock()->openLink('Create an Account'); $this->customerAccountCreate->getRegisterForm()->registerCustomer($customer); diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestStep/LogoutCustomerOnFrontendStep.php b/dev/tests/functional/tests/app/Magento/Customer/Test/TestStep/LogoutCustomerOnFrontendStep.php index 1ff30b1dcdf030fedef0e289d4b4d6c8076fc2f3..63399666bbc44a8ccd21371b6df5c5813bb2cf3e 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestStep/LogoutCustomerOnFrontendStep.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestStep/LogoutCustomerOnFrontendStep.php @@ -54,7 +54,7 @@ class LogoutCustomerOnFrontendStep implements TestStepInterface { $this->customerAccount->open(); $this->cmsIndex->getCmsPageBlock()->waitPageInit(); - if ($this->cmsIndex->getLinksBlock()->isLinkVisible('Sign Out')) { + if ($this->cmsIndex->getTitleBlock()->getTitle() != 'Customer Login') { $this->cmsIndex->getLinksBlock()->openLink('Sign Out'); $this->cmsIndex->getCmsPageBlock()->waitUntilTextIsVisible('Home Page'); $this->cmsIndex->getCmsPageBlock()->waitPageInit(); diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/etc/webapi/di.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/etc/webapi/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..f6bea2a0ec5a86821934152c13ed90d4e220a6f4 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/etc/webapi/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <preference for="Magento\Customer\Test\Handler\Customer\CustomerInterface" type="\Magento\Customer\Test\Handler\Customer\Webapi" /> +</config> 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 a6daf3cbaf38f42758a1076b933de63a36a44c87..a9e54ed517058c2711cdc2096426944a0a04be6f 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 @@ -7,7 +7,7 @@ --> <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"> - <variation name="OnePageCheckoutTestVariation20" summary="Use DHL International (EU) Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12850"> + <variation name="OnePageCheckoutDhlTestVariation1" summary="Use DHL International (EU) Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12850"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">login</data> <data name="customer/dataset" xsi:type="string">customer_DE</data> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php index 9bb1f70601cd3e2330df8e9dd023e65cafed9713..70c1441e75a91df1fb5b96c97c891e8ff644375e 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php @@ -30,7 +30,7 @@ class Downloadable extends Tab * * @var string */ - protected $downloadableBlock = '//dl[@id="tab_content_downloadableInfo"]'; + protected $downloadableBlock = '[data-tab-type="tab_content_downloadableInfo"]'; /** * Get Downloadable block @@ -45,7 +45,7 @@ class Downloadable extends Tab $element = $element ?: $this->_rootElement; return $this->blockFactory->create( 'Magento\Downloadable\Test\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable\\' . $type, - ['element' => $element->find($this->downloadableBlock, Locator::SELECTOR_XPATH)] + ['element' => $element->find($this->downloadableBlock, Locator::SELECTOR_CSS)] ); } @@ -54,6 +54,7 @@ class Downloadable extends Tab * * @param array|null $fields * @param SimpleElement|null $element + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return array */ public function getDataFormTab($fields = null, SimpleElement $element = null) @@ -78,6 +79,7 @@ class Downloadable extends Tab * * @param array $fields * @param SimpleElement|null $element + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return $this */ public function fillFormTab(array $fields, SimpleElement $element = null) diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinkRow.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinkRow.xml index b16d5bcee163fd3980f7b6fe32d4de7fa2e34549..e1d0035c885a145ef79b6e6575d202bc810c967e 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinkRow.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinkRow.xml @@ -59,9 +59,5 @@ <selector>[name$='[link_url]']</selector> <strategy>css selector</strategy> </file_link_url> - <sort_order> - <selector>[name$='[sort_order]']</selector> - <strategy>css selector</strategy> - </sort_order> </fields> </mapping> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php index cd799e849f69eb2f382ca4bf674bf588c845696d..11657b4c78183519d31ddf11eae79a83cdd14ad6 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php @@ -16,13 +16,6 @@ use Magento\Mtf\Client\Element\SimpleElement; */ class Links extends Form { - /** - * 'Show Links block' button - * - * @var string - */ - protected $showLinks = '//*[@id="dt-links"]/a'; - /** * 'Add New Row for links' button * @@ -49,7 +42,7 @@ class Links extends Form * * @var string */ - protected $addLinkButtonBlock = '#dd-links .col-actions-add:last-child'; + protected $addLinkButtonBlock = '[data-ui-id=downloadable-links] .col-actions-add:last-child'; /** * Get Downloadable link item block @@ -77,9 +70,6 @@ class Links extends Form public function fillLinks(array $fields, SimpleElement $element = null) { $element = $element ?: $this->_rootElement; - if (!$element->find($this->title, Locator::SELECTOR_XPATH)->isVisible()) { - $element->find($this->showLinks, Locator::SELECTOR_XPATH)->click(); - } $mapping = $this->dataMapping( ['title' => $fields['title'], 'links_purchased_separately' => $fields['links_purchased_separately']] ); @@ -87,7 +77,6 @@ class Links extends Form foreach ($fields['downloadable']['link'] as $index => $link) { $rowBlock = $this->getRowBlock($index, $element); if (!$rowBlock->isVisible()) { - $element->find($this->addLinkButtonBlock)->click(); $element->find($this->addNewLinkRow, Locator::SELECTOR_XPATH)->click(); } $rowBlock->fillLinkRow($link); @@ -104,9 +93,6 @@ class Links extends Form public function getDataLinks(array $fields = null, SimpleElement $element = null) { $element = $element ?: $this->_rootElement; - if (!$element->find($this->title, Locator::SELECTOR_XPATH)->isVisible()) { - $element->find($this->showLinks, Locator::SELECTOR_XPATH)->click(); - } $mapping = $this->dataMapping( ['title' => $fields['title'], 'links_purchased_separately' => $fields['links_purchased_separately']] ); diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SampleRow.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SampleRow.xml index 045720f2d29e94b12fc3b9d0117df5a0e5e14ee9..e957de8cecb47f43302995739f2502f9f98f7034 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SampleRow.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SampleRow.xml @@ -25,9 +25,5 @@ <selector>[name$='[sample_url]']</selector> <strategy>css selector</strategy> </sample_url> - <sort_order> - <selector>[name$='[sort_order]']</selector> - <strategy>css selector</strategy> - </sort_order> </fields> </mapping> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php index 5678ebd679fbbc179ba717b14214246c368e7730..cf28a2009661d1e9517126f630409e3c10329c69 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php @@ -23,20 +23,6 @@ class Samples extends Form */ protected $addNewSampleRow = '//button[@id="add_sample_item"]'; - /** - * 'Show Sample block' button - * - * @var string - */ - protected $showSample = '//dt[@id="dt-samples"]/a'; - - /** - * Sample title block - * - * @var string - */ - protected $samplesTitle = '//input[@name="product[samples_title]"]'; - /** * Downloadable sample item block * @@ -70,9 +56,6 @@ class Samples extends Form public function fillSamples(array $fields = null, SimpleElement $element = null) { $element = $element ?: $this->_rootElement; - if (!$element->find($this->samplesTitle, Locator::SELECTOR_XPATH)->isVisible()) { - $element->find($this->showSample, Locator::SELECTOR_XPATH)->click(); - } $mapping = $this->dataMapping(['title' => $fields['title']]); $this->_fill($mapping); foreach ($fields['downloadable']['sample'] as $index => $sample) { @@ -91,9 +74,6 @@ class Samples extends Form public function getDataSamples(array $fields = null, SimpleElement $element = null) { $element = $element ?: $this->_rootElement; - if (!$element->find($this->samplesTitle, Locator::SELECTOR_XPATH)->isVisible()) { - $element->find($this->showSample, Locator::SELECTOR_XPATH)->click(); - } $mapping = $this->dataMapping(['title' => $fields['title']]); $newFields = $this->_getData($mapping); foreach ($fields['downloadable']['sample'] as $index => $sample) { diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Fixture/DownloadableProduct.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Fixture/DownloadableProduct.xml index 3d250fc3f2f46bf059b105c988536531a4a07afc..f75a1b3fb39d1a74459c913e4c35619ad6b4b7ff 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Fixture/DownloadableProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Fixture/DownloadableProduct.xml @@ -34,7 +34,6 @@ <field name="description" is_required="0" group="product-details" /> <field name="gallery" is_required="0" /> <field name="gift_message_available" is_required="0" /> - <field name="group_price" is_required="0" group="advanced-pricing" repository="Magento\Catalog\Test\Repository\Product\GroupPriceOptions" /> <field name="has_options" is_required="0" /> <field name="image" is_required="0" /> <field name="image_label" is_required="0" /> @@ -86,7 +85,7 @@ <field name="weight" is_required="0" group="product-details" /> <field name="custom_options" is_required="0" group="customer-options" source="Magento\Catalog\Test\Fixture\Product\CustomOptions" repository="Magento\Catalog\Test\Repository\Product\CustomOptions" /> <field name="id" /> - <field name="is_virtual" group="product-details" /> + <field name="product_has_weight" group="product-details" /> <field name="website_ids" group="websites" /> <field name="checkout_data" group="null" repository="Magento\Downloadable\Test\Repository\DownloadableProduct\CheckoutData" /> <field name="cross_sell_products" group="crosssells" source="Magento\Catalog\Test\Fixture\Product\RelatedProducts" /> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Curl.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Curl.php index 90e29e11862457cfa09dd6b56b88f5f15004d671..5e78ac6fb83397ba481a4d3f24b9d8abfea0d9a2 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Curl.php @@ -84,7 +84,6 @@ class Curl extends ProductCurl implements DownloadableProductInterface $data['downloadable']['link'][$key]['price'] = $link['price']; $data['downloadable']['link'][$key]['number_of_downloads'] = $link['number_of_downloads']; $data['downloadable']['link'][$key]['is_shareable'] = $link['is_shareable']; - $data['downloadable']['link'][$key]['sort_order'] = $link['sort_order']; $data['downloadable']['link'][$key]['sample']['type'] = 'url'; $data['downloadable']['link'][$key]['sample']['url'] = $link['sample']['sample_url']; } diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct.xml index 8d84025eee8b4ca63eccf2f1d24b16ce654baa40..07f31ad2e8fb2e311f51c14270e24b5a132a1e4f 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct.xml @@ -25,7 +25,7 @@ <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> <field name="url_key" xsi:type="string">test-downloadable-product-%isolation%</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="downloadable_links" xsi:type="array"> <item name="dataset" xsi:type="string">with_two_separately_links</item> </field> @@ -53,7 +53,7 @@ </field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -81,7 +81,7 @@ </field> <field name="status" xsi:type="string">Product online</field> <field name="visibility" xsi:type="string">Catalog, Search</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -113,41 +113,7 @@ <item name="dataset" xsi:type="string">default_subcategory</item> </field> <field name="visibility" xsi:type="string">Catalog, Search</field> - <field name="is_virtual" xsi:type="string">Yes</field> - <field name="website_ids" xsi:type="array"> - <item name="0" xsi:type="string">Main Website</item> - </field> - <field name="downloadable_links" xsi:type="array"> - <item name="dataset" xsi:type="string">with_two_separately_links</item> - </field> - <field name="checkout_data" xsi:type="array"> - <item name="dataset" xsi:type="string">downloadable_with_two_bought_links</item> - </field> - </dataset> - - <dataset name="with_two_separately_links_group_price_and_category"> - <field name="name" xsi:type="string">Downloadable product %isolation%</field> - <field name="sku" xsi:type="string">downloadable_product_%isolation%</field> - <field name="url_key" xsi:type="string">downloadable-product-%isolation%</field> - <field name="price" xsi:type="array"> - <item name="value" xsi:type="string">30</item> - </field> - <field name="group_price" xsi:type="array"> - <item name="dataset" xsi:type="string">general_90_99</item> - </field> - <field name="tax_class_id" xsi:type="array"> - <item name="dataset" xsi:type="string">taxable_goods</item> - </field> - <field name="quantity_and_stock_status" xsi:type="array"> - <item name="qty" xsi:type="string">1111</item> - <item name="is_in_stock" xsi:type="string">In Stock</item> - </field> - <field name="status" xsi:type="string">Product online</field> - <field name="category_ids" xsi:type="array"> - <item name="dataset" xsi:type="string">default_subcategory</item> - </field> - <field name="visibility" xsi:type="string">Catalog, Search</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -178,7 +144,7 @@ <item name="dataset" xsi:type="string">default_subcategory</item> </field> <field name="visibility" xsi:type="string">Catalog, Search</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="website_ids" xsi:type="array"> <item name="0" xsi:type="string">Main Website</item> </field> @@ -197,7 +163,7 @@ <field name="name" xsi:type="string">Downloadable product %isolation%</field> <field name="sku" xsi:type="string">downloadable_product_%isolation%</field> <field name="url_key" xsi:type="string">downloadable-product-%isolation%</field> - <field name="is_virtual" xsi:type="string">Yes</field> + <field name="product_has_weight" xsi:type="string">No</field> <field name="price" xsi:type="array"> <item name="value" xsi:type="string">1</item> </field> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Links.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Links.xml index 52291319fe9abe64a4dec81e8e44541c0bdd5dd9..5d335015da5068963f7b8cd01cc41d9293d10f76 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Links.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Links.xml @@ -17,7 +17,6 @@ <item name="price" xsi:type="string">2</item> <item name="is_shareable" xsi:type="string">Use config</item> <item name="number_of_downloads" xsi:type="string">2</item> - <item name="sort_order" xsi:type="string">1</item> <item name="sample" xsi:type="array"> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> @@ -39,7 +38,6 @@ <item name="price" xsi:type="string">2</item> <item name="is_shareable" xsi:type="string">Use config</item> <item name="number_of_downloads" xsi:type="string">2</item> - <item name="sort_order" xsi:type="string">1</item> <item name="sample" xsi:type="array"> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> @@ -67,7 +65,6 @@ <item name="file_type_url" xsi:type="string">Yes</item> <item name="file_link_url" xsi:type="string">http://example.com</item> <item name="is_shareable" xsi:type="string">No</item> - <item name="sort_order" xsi:type="string">1</item> </item> <item name="1" xsi:type="array"> <item name="title" xsi:type="string">link-2-%isolation%</item> @@ -80,7 +77,6 @@ <item name="file_type_url" xsi:type="string">Yes</item> <item name="file_link_url" xsi:type="string">http://example.com</item> <item name="is_shareable" xsi:type="string">Yes</item> - <item name="sort_order" xsi:type="string">0</item> </item> </item> </field> @@ -102,7 +98,6 @@ <item name="file_type_url" xsi:type="string">Yes</item> <item name="file_link_url" xsi:type="string">http://example.com</item> <item name="is_shareable" xsi:type="string">No</item> - <item name="sort_order" xsi:type="string">0</item> </item> <item name="1" xsi:type="array"> <item name="title" xsi:type="string">link-2-%isolation%</item> @@ -115,7 +110,6 @@ <item name="file_type_url" xsi:type="string">Yes</item> <item name="file_link_url" xsi:type="string">http://example.com</item> <item name="is_shareable" xsi:type="string">Yes</item> - <item name="sort_order" xsi:type="string">1</item> </item> <item name="2" xsi:type="array"> <item name="title" xsi:type="string">link-3-%isolation%</item> @@ -128,7 +122,6 @@ <item name="file_type_url" xsi:type="string">Yes</item> <item name="file_link_url" xsi:type="string">http://example.com</item> <item name="is_shareable" xsi:type="string">Yes</item> - <item name="sort_order" xsi:type="string">2</item> </item> </item> </field> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Samples.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Samples.xml index 65bf205651d1de2990be96e4edc795009b42c41c..566c339d43e36897a5da6859d63bd825ca5e6374 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Samples.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Repository/DownloadableProduct/Samples.xml @@ -15,13 +15,11 @@ <item name="title" xsi:type="string">sample1%isolation%</item> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> - <item name="sort_order" xsi:type="string">0</item> </item> <item name="1" xsi:type="array"> <item name="title" xsi:type="string">sample2%isolation%</item> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> - <item name="sort_order" xsi:type="string">1</item> </item> </item> </field> @@ -35,19 +33,16 @@ <item name="title" xsi:type="string">sample1%isolation%</item> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> - <item name="sort_order" xsi:type="string">0</item> </item> <item name="1" xsi:type="array"> <item name="title" xsi:type="string">sample2%isolation%</item> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> - <item name="sort_order" xsi:type="string">1</item> </item> <item name="2" xsi:type="array"> <item name="title" xsi:type="string">sample3%isolation%</item> <item name="sample_type_url" xsi:type="string">Yes</item> <item name="sample_url" xsi:type="string">http://example.com</item> - <item name="sort_order" xsi:type="string">2</item> </item> </item> </field> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml index 67da232884b055c6582ebba8f6f813b01a3e2b81..5c8e12cbb0e1a2ed8bb50c24311a3af37987acb0 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/CreateDownloadableProductEntityTest.xml @@ -29,7 +29,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">1</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="product/data/url_key" xsi:type="string">downloadableproduct-%isolation%</data> @@ -48,7 +48,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/downloadable_sample/dataset" xsi:type="string">with_two_samples</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> @@ -68,7 +68,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="product/data/custom_options/dataset" xsi:type="string">default</data> @@ -88,7 +88,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/downloadable_sample/dataset" xsi:type="string">with_three_samples</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_three_links</data> <data name="product/data/custom_options/dataset" xsi:type="string">two_options</data> @@ -111,7 +111,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">50</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="product/data/url_key" xsi:type="string">downloadableproduct-%isolation%</data> @@ -126,7 +126,7 @@ <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">9999</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/stock_data/manage_stock" xsi:type="string">Yes</data> <data name="product/data/stock_data/qty" xsi:type="string">123</data> @@ -147,7 +147,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">5</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/description" xsi:type="string">This is description for downloadable product</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> @@ -165,7 +165,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/short_description" xsi:type="string">This is short description for downloadable product</data> <data name="product/data/downloadable_sample/dataset" xsi:type="string">with_two_samples</data> @@ -189,7 +189,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">11</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/description" xsi:type="string">This is description for downloadable product</data> <data name="product/data/short_description" xsi:type="string">This is short description for downloadable product</data> @@ -213,7 +213,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">11</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/downloadable_sample/dataset" xsi:type="string">with_two_samples</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_three_links</data> @@ -232,7 +232,7 @@ <data name="product/data/sku" xsi:type="string">DownloadableProduct_%isolation%</data> <data name="product/data/price/value" xsi:type="string">100</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="product/data/url_key" xsi:type="string">downloadableproduct-%isolation%</data> @@ -249,7 +249,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="product/data/special_price" xsi:type="string">5</data> @@ -268,10 +268,9 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">23</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> - <data name="product/data/group_price/dataset" xsi:type="string">not_logged_in_20</data> <data name="product/data/url_key" xsi:type="string">downloadableproduct-%isolation%</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> @@ -287,7 +286,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">65</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="product/data/tier_price/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..3eefc2707c0aee440522a6ead9a4d6053b500418 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\DeleteProductEntityTest"> + <variation name="DeleteProductEntityTestVariation7" firstConstraint="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" method="test"> + <data name="products" xsi:type="string">downloadableProduct::default</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..5b18f4e3521337ea6d90ea8d1f60732e2a7454e2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\Checkout\Test\TestCase\DeleteProductFromMiniShoppingCartTest" summary="Delete Downloadable Product from Mini Shopping Cart" ticketId="MAGETWO-29104"> + <variation name="DeleteDownloadableProductFromMiniShoppingCartTestVariation"> + <data name="products" xsi:type="string">downloadableProduct::default</data> + <data name="deletedProductIndex" xsi:type="string">0</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/TaxCalculationTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/TaxCalculationTest.xml index d1bc12b23b8e5f5152e28fc26e116868dcc78df0..77c56abc3d66b77c6d65eeac393c55a3f963aa7c 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/TaxCalculationTest.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/TaxCalculationTest.xml @@ -52,26 +52,6 @@ <constraint name="Magento\Downloadable\Test\Constraint\AssertTaxCalculationAfterCheckoutDownloadableIncludingTax" /> <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendIncludingTax" /> </variation> - <variation name="DownloadableTaxCalculationTestVariation3" summary="Downloadable product with sales rule, customer tax less than store tax and catalog price excluding tax" ticketId="MAGETWO-32076"> - <data name="configData" xsi:type="string">total_cat_excl_ship_incl_after_disc_on_incl</data> - <data name="product" xsi:type="string">downloadableProduct::with_two_separately_links_group_price_and_category</data> - <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> - <data name="catalogRule" xsi:type="string">-</data> - <data name="taxRule" xsi:type="string">customer_less_store_rate</data> - <data name="customer/dataset" xsi:type="string">johndoe_unique</data> - <data name="qty" xsi:type="string">-</data> - <data name="prices/category_price" xsi:type="string">30.00</data> - <data name="prices/product_view_price" xsi:type="string">35.43</data> - <data name="prices/cart_item_price_excl_tax" xsi:type="string">35.43</data> - <data name="prices/cart_item_subtotal_excl_tax" xsi:type="string">35.43</data> - <data name="prices/subtotal_excl_tax" xsi:type="string">35.43</data> - <data name="prices/discount" xsi:type="string">19.18</data> - <data name="prices/tax" xsi:type="string">1.34</data> - <data name="prices/grand_total_excl_tax" xsi:type="string">17.59</data> - <constraint name="Magento\Downloadable\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesDownloadableExcludingTax" /> - <constraint name="Magento\Downloadable\Test\Constraint\AssertTaxCalculationAfterCheckoutDownloadableExcludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingTax" /> - </variation> <variation name="DownloadableTaxCalculationTestVariation4" summary="Downloadable product with catalog rule, customer tax greater than store tax and catalog price including tax" ticketId="MAGETWO-32076"> <data name="configData" xsi:type="string">total_cat_incl_ship_excl_before_disc_on_excl, display_excluding_including_tax</data> <data name="product" xsi:type="string">downloadableProduct::with_two_separately_links_custom_options_and_category</data> @@ -96,27 +76,6 @@ <constraint name="Magento\Downloadable\Test\Constraint\AssertTaxCalculationAfterCheckoutDownloadableExcludingIncludingTax" /> <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> </variation> - <variation name="DownloadableTaxCalculationTestVariation5" summary="Downloadable product with sales rule, customer tax less than store tax and catalog price including tax" ticketId="MAGETWO-32076"> - <data name="configData" xsi:type="string">total_cat_incl_ship_excl_before_disc_on_excl, display_including_tax</data> - <data name="product" xsi:type="string">downloadableProduct::with_two_separately_links_group_price_and_category</data> - <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> - <data name="catalogRule" xsi:type="string">-</data> - <data name="taxRule" xsi:type="string">customer_less_store_rate</data> - <data name="customer/dataset" xsi:type="string">johndoe_unique</data> - <data name="qty" xsi:type="string">-</data> - <data name="prices/category_price_incl_tax" xsi:type="string">19.98</data> - <data name="prices/product_view_price_incl_tax" xsi:type="string">22.98</data> - <data name="prices/cart_item_price_incl_tax" xsi:type="string">22.97</data> - <data name="prices/cart_item_subtotal_incl_tax" xsi:type="string">22.97</data> - <data name="prices/subtotal_incl_tax" xsi:type="string">22.97</data> - <data name="prices/discount" xsi:type="string">10.61</data> - <data name="prices/tax" xsi:type="string">1.75</data> - <data name="prices/grand_total_excl_tax" xsi:type="string">10.61</data> - <data name="prices/grand_total_incl_tax" xsi:type="string">12.36</data> - <constraint name="Magento\Downloadable\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesDownloadableIncludingTax" /> - <constraint name="Magento\Downloadable\Test\Constraint\AssertTaxCalculationAfterCheckoutDownloadableIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendIncludingTax" /> - </variation> <variation name="DownloadableTaxCalculationTestVariation6" summary="Downloadable product with catalog rule, customer tax equals store tax and catalog price including tax" ticketId="MAGETWO-32076"> <data name="configData" xsi:type="string">total_cat_incl_ship_excl_before_disc_on_excl</data> <data name="product" xsi:type="string">downloadableProduct::with_two_separately_links_custom_options_and_category</data> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/UpdateDownloadableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/UpdateDownloadableProductEntityTest.xml index e1a51bf8cc9d5991d4f2c5a90fdfeb39d7f55ff1..8a5230fce71e3e4b8aa5d9be21cc025f8c170fe0 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/UpdateDownloadableProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/UpdateDownloadableProductEntityTest.xml @@ -14,7 +14,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/downloadable_sample/dataset" xsi:type="string">with_three_samples</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_three_links</data> <data name="product/data/custom_options/dataset" xsi:type="string">two_options</data> @@ -36,7 +36,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">50</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">Out of Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> <data name="isRequired" xsi:type="string">No</data> @@ -52,7 +52,7 @@ <data name="product/data/price/value" xsi:type="string">9999</data> <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">123</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/stock_data/manage_stock" xsi:type="string">Yes</data> <data name="product/data/stock_data/use_config_min_qty" xsi:type="string">No</data> @@ -71,7 +71,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">None</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">5</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">Default Category</data> <data name="product/data/description" xsi:type="string">This is description for downloadable product</data> <data name="product/data/downloadable_links/dataset" xsi:type="string">with_two_separately_links</data> @@ -89,7 +89,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/category" xsi:type="string">category %isolation%</data> <data name="product/data/short_description" xsi:type="string">This is short description for downloadable product</data> <data name="product/data/downloadable_sample/dataset" xsi:type="string">with_two_samples</data> @@ -113,7 +113,7 @@ <data name="product/data/tax_class_id/dataset" xsi:type="string">taxable_goods</data> <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">10</data> <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/is_virtual" xsi:type="string">Yes</data> + <data name="product/data/product_has_weight" xsi:type="string">No</data> <data name="product/data/special_price" xsi:type="string">40</data> <data name="isRequired" xsi:type="string">No</data> <data name="product/data/url_key" xsi:type="string">downloadableproduct-%isolation%</data> 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 2e0864df7daba455eec74359bad4fa8f45093a13..d53852fef4800cd19fa6adbf896c89cf29c983c6 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 @@ -7,8 +7,7 @@ --> <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"> - <variation name="OnePageCheckoutTestVariation22"> - <data name="description" xsi:type="string">Check Out as Guest using FedEx with US shipping origin and UK customer</data> + <variation name="OnePageCheckoutFedexTestVariation1" summary="Check Out as Guest using FedEx with US shipping origin and UK customer"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">guest</data> <data name="customer/dataset" xsi:type="string">default</data> @@ -23,7 +22,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty"/> </variation> - <variation name="OnePageCheckoutTestVariation23" summary="MAGETWO-12849" ticketId="Use FedEx Online Shipping Carrier on Checkout as a Registered Customer"> + <variation name="OnePageCheckoutFedexTestVariation2" summary="Use FedEx Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12849"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">login</data> <data name="customer/dataset" xsi:type="string">customer_DE</data> diff --git a/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Block/Cart/Item/GiftOptions.php b/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Block/Cart/Item/GiftOptions.php index 78ce8fe619261744f1a8dfd817ea6980fce21a32..544327424866940ec5ad1858d015fff2fe07bb7f 100644 --- a/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Block/Cart/Item/GiftOptions.php +++ b/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Block/Cart/Item/GiftOptions.php @@ -55,7 +55,7 @@ class GiftOptions extends Form /** @var \Magento\GiftMessage\Test\Block\Cart\GiftOptions\GiftMessageForm $giftMessageForm */ if ($giftMessage->getAllowGiftOptionsForItems() === 'Yes') { foreach ($products as $product) { - if ($product->getIsVirtual() !== 'Yes') { + if ($product->getProductHasWeight() == 'Yes') { $this->_rootElement->find( sprintf($this->allowGiftOptions, $product->getName()), Locator::SELECTOR_XPATH diff --git a/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Constraint/AssertGiftMessageInFrontendOrderItems.php b/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Constraint/AssertGiftMessageInFrontendOrderItems.php index bb193414ea93a70f62b7e64ce4281c4ba87dd0d9..9adbac82bef1afdf11d58df03d306f0c34bd98d2 100644 --- a/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Constraint/AssertGiftMessageInFrontendOrderItems.php +++ b/dev/tests/functional/tests/app/Magento/GiftMessage/Test/Constraint/AssertGiftMessageInFrontendOrderItems.php @@ -62,7 +62,7 @@ class AssertGiftMessageInFrontendOrderItems extends AbstractConstraint 'message' => $itemGiftMessage->getMessage(), ]; } - if ($product->getIsVirtual() == 'Yes') { + if ($product->getProductHasWeight() !== 'Yes') { $expectedData = []; } diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Cart/Sidebar.php b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Cart/Sidebar.php new file mode 100644 index 0000000000000000000000000000000000000000..259eaab021c6c262c55598a9663bbf37d74fa008 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Cart/Sidebar.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\GroupedProduct\Test\Block\Cart; + +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\GroupedProduct\Test\Fixture\GroupedProduct; +use Magento\Checkout\Test\Block\Cart\Sidebar as MiniShoppingCart; + +/** + * Mini shopping cart block. + */ +class Sidebar extends MiniShoppingCart +{ + /** + * Get cart item block. + * + * @param FixtureInterface $product + * @return \Magento\GroupedProduct\Test\Block\Cart\Sidebar\Item + */ + public function getCartItem(FixtureInterface $product) + { + return $this->blockFactory->create( + 'Magento\GroupedProduct\Test\Block\Cart\Sidebar\Item', + [ + 'element' => $this->_rootElement, + 'config' => [ + 'associated_cart_items' => $this->findCartItems($product), + ] + ] + ); + } + + /** + * Find cart item blocks for associated products. + * + * @param FixtureInterface $product + * @return array + */ + protected function findCartItems(FixtureInterface $product) + { + $cartItems = []; + + /** @var GroupedProduct $product */ + $associatedProducts = $product->getAssociated()['products']; + foreach ($associatedProducts as $product) { + $cartItems[$product->getSku()] = parent::getCartItem($product); + } + + return $cartItems; + } +} diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Cart/Sidebar/Item.php b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Cart/Sidebar/Item.php new file mode 100644 index 0000000000000000000000000000000000000000..a9bbf2f8ae33108f01a756380183c159f21d0fa3 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Cart/Sidebar/Item.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\GroupedProduct\Test\Block\Cart\Sidebar; + +use Magento\Checkout\Test\Block\Cart\Sidebar\Item as ProductItem; + +/** + * Grouped Product item block on mini Cart. + */ +class Item extends ProductItem +{ + /** + * Remove grouped product item from mini cart. + * + * @return void + */ + public function removeItemFromMiniCart() + { + foreach ($this->config['associated_cart_items'] as $productItem) { + /** @var ProductItem $productItem */ + $productItem->removeItemFromMiniCart(); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Page/CmsIndex.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Page/CmsIndex.xml new file mode 100644 index 0000000000000000000000000000000000000000..73c7c92e6b77abc9bcfaede1fee006773d9fa97c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Page/CmsIndex.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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="CmsIndex" mca="cms/index/index"> + <block name="cartSidebarBlock"> + <render name="grouped" class="Magento\GroupedProduct\Test\Block\Cart\Sidebar"/> + </block> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml index 008eda5b50abe76b85ff5a286a7c0eac83fd83a2..6166f4dffe39fcdfb6599089f81c9ae190b5ae46 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml @@ -86,20 +86,6 @@ <constraint name="Magento\GroupedProduct\Test\Constraint\AssertGroupedProductForm" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> </variation> - <variation name="CreateGroupedProductEntityTestVariation7" firstConstraint="Magento\GroupedProduct\Test\Constraint\AssertGroupedPriceOnGroupedProductPage" method="test"> - <data name="description" xsi:type="string">Create with group price products</data> - <data name="product/data/url_key" xsi:type="string">test-grouped-product-%isolation%</data> - <data name="product/data/name" xsi:type="string">GroupedProduct %isolation%</data> - <data name="product/data/sku" xsi:type="string">GroupedProduct_sku%isolation%</data> - <data name="product/data/quantity_and_stock_status/is_in_stock" xsi:type="string">In Stock</data> - <data name="product/data/category" xsi:type="string">category_%isolation%</data> - <data name="product/data/associated/products" xsi:type="string">catalogProductSimple::simple_with_group_price,catalogProductSimple::simple_with_group_price</data> - <data name="product/data/associated/dataset" xsi:type="string">defaultSimpleProduct</data> - <data name="isRequired" xsi:type="string">No</data> - <constraint name="Magento\GroupedProduct\Test\Constraint\AssertGroupedPriceOnGroupedProductPage" /> - <constraint name="Magento\GroupedProduct\Test\Constraint\AssertGroupedProductForm" /> - <constraint name="Magento\Catalog\Test\Constraint\AssertProductPage" /> - </variation> <variation name="CreateGroupedProductEntityTestVariation8" firstConstraint="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" method="test"> <data name="description" xsi:type="string">Create with virtual products</data> <data name="product/data/url_key" xsi:type="string">test-grouped-product-%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f8e22cc953bc5b0c94f435dc69341da10c38893 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\DeleteProductEntityTest"> + <variation name="DeleteProductEntityTestVariation8" firstConstraint="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" method="test"> + <data name="products" xsi:type="string">groupedProduct::default</data> + <data name="isRequired" xsi:type="string">Yes</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..af0b284725291c71795a6ac08e8c7cdc14ba8f96 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductFromMiniShoppingCartTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\Checkout\Test\TestCase\DeleteProductFromMiniShoppingCartTest" summary="Delete Grouped Product from Mini Shopping Cart" ticketId="MAGETWO-29104"> + <variation name="DeleteGroupedProductFromMiniShoppingCartTestVariation"> + <data name="products" xsi:type="string">groupedProduct::default</data> + <data name="deletedProductIndex" xsi:type="string">0</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertLanguageSelected.php b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertLanguageSelected.php index e6de2178a4eeb04931bdebd038cf099513be41dd..100e32be0b442bb783891793ab88d2eb7725cda9 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertLanguageSelected.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Constraint/AssertLanguageSelected.php @@ -25,7 +25,7 @@ class AssertLanguageSelected extends AbstractConstraint { $indexPage->open(); \PHPUnit_Framework_Assert::assertTrue( - $indexPage->getLinksBlock()->isLinkVisible($languageTemplate), + $indexPage->getFooterBlock()->isLinkVisible($languageTemplate), 'Selected language not displays on frontend.' ); } diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php index 4dfa6dd2122c10aa6a7344210cd47d10985551c0..256d0caa1d980bfe82719e296395b8ed9e89d69a 100644 --- a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php +++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php @@ -15,63 +15,87 @@ use Magento\Mtf\Client\Locator; class Navigation extends Block { /** - * 'Clear All' link. + * Locator for loaded "narrow-by-list" block. + * + * @var string + */ + protected $loadedNarrowByList = '#narrow-by-list[role="tablist"]'; + + /** + * Locator value for "Clear All" link. * * @var string */ protected $clearAll = '.action.clear'; /** - * Attribute option title selector. + * Locator value for correspondent Attribute filter option. + * + * @var string + */ + protected $optionTitle = './/div[@class="filter-options-title" and contains(text(),"%s")]'; + + /** + * Locator value for correspondent "Filter" link. * * @var string */ - protected $optionTitle = '.filter-options-title'; + protected $filterLink = './/div[@class="filter-options-title" and contains(text(),"%s")]/following-sibling::div//a'; /** - * Filter link locator. + * Locator value for "Expand Filter" button. * * @var string */ - protected $filterLink = './/dt[contains(text(),"%s")]/following-sibling::dd//a'; + protected $expandFilterButton = '[data]'; /** - * Click on 'Clear All' link. + * Remove all applied filters. * * @return void */ public function clearAll() { - $this->_rootElement->find($this->clearAll, locator::SELECTOR_CSS)->click(); + $this->_rootElement->find($this->clearAll)->click(); } /** - * Get array of available filters. + * Get all available filters. * * @return array */ public function getFilters() { - $options = $this->_rootElement->getElements($this->optionTitle); + $this->waitForElementVisible($this->loadedNarrowByList); + + $options = $this->_rootElement->getElements(sprintf($this->optionTitle, ''), Locator::SELECTOR_XPATH); $data = []; foreach ($options as $option) { - $data[] = $option->getText(); + $data[] = strtoupper($option->getText()); } + return $data; } /** - * Click filter link. + * Apply Layerd Navigation filter. * * @param string $filter * @param string $linkPattern * @return void * @throws \Exception */ - public function clickFilterLink($filter, $linkPattern) + public function applyFilter($filter, $linkPattern) { - $links = $this->_rootElement->getElements(sprintf($this->filterLink, $filter), Locator::SELECTOR_XPATH); + $expandFilterButton = sprintf($this->optionTitle, $filter); + $links = sprintf($this->filterLink, $filter); + + $this->waitForElementVisible($this->loadedNarrowByList); + if (!$this->_rootElement->find($links, Locator::SELECTOR_XPATH)->isVisible()) { + $this->_rootElement->find($expandFilterButton, Locator::SELECTOR_XPATH)->click(); + } + $links = $this->_rootElement->getElements($links, Locator::SELECTOR_XPATH); foreach ($links as $link) { if (preg_match($linkPattern, trim($link->getText()))) { $link->click(); diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php index e2b384a26611026f3c353fd7d8f7185ecab99143..9d840ff45dd61853c222e5b107f4ecc5febe5466 100644 --- a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php +++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php @@ -44,7 +44,7 @@ class AssertFilterProductList extends AbstractConstraint foreach ($layeredNavigation as $filters) { foreach ($filters as $filter) { - $catalogCategoryView->getLayeredNavigationBlock()->clickFilterLink( + $catalogCategoryView->getLayeredNavigationBlock()->applyFilter( $filter['title'], $filter['linkPattern'] ); diff --git a/dev/tests/functional/tests/app/Magento/Msrp/Test/Repository/CatalogProductSimple.xml b/dev/tests/functional/tests/app/Magento/Msrp/Test/Repository/CatalogProductSimple.xml index 4553883e0c2a7e43576530662313c408b242761b..f3ee8cb520a74c7b329223d225e5a28d38455a98 100644 --- a/dev/tests/functional/tests/app/Magento/Msrp/Test/Repository/CatalogProductSimple.xml +++ b/dev/tests/functional/tests/app/Magento/Msrp/Test/Repository/CatalogProductSimple.xml @@ -14,6 +14,7 @@ <field name="name" xsi:type="string">Simple Product with msrp %isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_with_msrp_%isolation%</field> <field name="weight" xsi:type="string">1</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">25</item> <item name="is_in_stock" xsi:type="string">In Stock</item> @@ -41,6 +42,7 @@ <field name="url_key" xsi:type="string">simple-product-with-msrp-%isolation%</field> <field name="sku" xsi:type="string">sku_simple_product_with_msrp_%isolation%</field> <field name="weight" xsi:type="string">1</field> + <field name="product_has_weight" xsi:type="string">Yes</field> <field name="quantity_and_stock_status" xsi:type="array"> <item name="qty" xsi:type="string">1000</item> <item name="is_in_stock" xsi:type="string">In Stock</item> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/CreditMemos.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/CreditMemos.php index 2ebb040791cdcecaa3a26ee06e26494ce04919ce..01a177c91ccad192574b772fd1ccda355d2ab285 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/CreditMemos.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/CreditMemos.php @@ -10,20 +10,19 @@ use Magento\Backend\Test\Block\Widget\Tab; use Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\CreditMemos\Grid; /** - * Class CreditMemos - * CreditMemos tab + * CreditMemos tab. */ class CreditMemos extends Tab { /** - * Grid block css selector + * Grid block css selector. * * @var string */ protected $grid = '#sales_order_view_tabs_order_creditmemos_content'; /** - * Get grid block + * Get grid block. * * @return Grid */ diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/Shipments.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/Shipments.php index 38b4e2344fd4b0701b29b9a519d8280090ca5155..9e5725bcf5c0582afbb0ec09fb460052d8cafcf6 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/Shipments.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/View/Tab/Shipments.php @@ -10,20 +10,19 @@ use Magento\Backend\Test\Block\Widget\Tab; use Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\Shipments\Grid; /** - * Class Shipments - * Shipments tab + * Shipments tab. */ class Shipments extends Tab { /** - * Grid block css selector + * Grid block css selector. * * @var string */ protected $grid = '#sales_order_view_tabs_order_shipments_content'; /** - * Get grid block + * Get grid block. * * @return Grid */ diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php new file mode 100644 index 0000000000000000000000000000000000000000..a1e89784737ae43635a12599a3ff534ed65c86c4 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Handler/OrderInjectable/Webapi.php @@ -0,0 +1,329 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Sales\Test\Handler\OrderInjectable; + +use Magento\Bundle\Test\Fixture\BundleProduct; +use Magento\ConfigurableProduct\Test\Fixture\ConfigurableProduct; +use Magento\Downloadable\Test\Fixture\DownloadableProduct; +use Magento\Sales\Test\Fixture\OrderInjectable; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; +use Magento\Mtf\Handler\Webapi as AbstractWebapi; +use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; + +/** + * Create new order via web API. + */ +class Webapi extends AbstractWebapi implements OrderInjectableInterface +{ + /** + * Mapping values for data. + * + * @var array + */ + protected $mappingData = [ + 'region_id' => [ + 'California' => '12', + ], + 'country_id' => [ + 'United States' => 'US', + ], + ]; + + /** + * Order quote value. + * + * @var string + */ + protected $quote; + + /** + * First part of Web API url for creating order. + * + * @var string + */ + protected $url; + + /** + * Creating order using quote via web API. + * + * @param FixtureInterface|null $fixture [optional] + * @return array + */ + public function persist(FixtureInterface $fixture = null) + { + /** @var OrderInjectable $fixture */ + $this->createQuote($fixture); + $this->url = $_ENV['app_frontend_url'] . 'rest/V1/carts/' . (int)$this->quote; + $this->setProducts($fixture); + $this->setCoupon($fixture); + $this->setAddress($fixture, 'billing'); + $this->setAddress($fixture, 'shipping'); + $this->setShippingMethod($fixture); + $this->setPaymentMethod($fixture); + $orderId = $this->placeOrder(); + + return ['id' => sprintf("%09d", $orderId)]; + } + + /** + * Create checkout quote. + * + * @param OrderInjectable $order + * @return void + * @throws \Exception + */ + protected function createQuote(OrderInjectable $order) + { + $url = $_ENV['app_frontend_url'] . 'rest/V1/customers/' . $order->getCustomerId()->getId() . '/carts'; + $data = '{"customerId": "' . $order->getCustomerId()->getId() . '"}'; + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if (!is_numeric($response)) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Could not create checkout quote viw web API!'); + } + $this->quote = $response; + } + + /** + * Add products to quote. + * + * @param OrderInjectable $order + * @return void + * @throws \Exception + */ + protected function setProducts(OrderInjectable $order) + { + $url = $_ENV['app_frontend_url'] . 'rest/V1/carts/items'; + $products = $order->getEntityId()['products']; + foreach ($products as $product) { + $data = [ + 'cartItem' => [ + 'sku' => $product->getSku(), + 'qty' => isset($product->getCheckoutData()['qty']) ? $product->getCheckoutData()['qty'] : 1, + 'quote_id' => $this->quote + ] + ]; + $methodName = 'prepare' . ucfirst($product->getDataConfig()['type_id']) . 'Options'; + if (method_exists($this, $methodName)) { + $data['cartItem']['product_option'] = $this->$methodName($product); + } + $this->webapiTransport->write($url, $data); + $response = (array)json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if (isset($response['message'])) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Could not add product item to quote!'); + } + } + } + + /** + * Set coupon to quote. + * + * @param OrderInjectable $order + * @return void + * @throws \Exception + */ + protected function setCoupon(OrderInjectable $order) + { + if (!$order->hasData('coupon_code')) { + return; + } + $url = $this->url . '/coupons/' . $order->getCouponCode()->getCouponCode(); + $data = [ + 'cartId' => $this->quote, + 'couponCode' => $order->getCouponCode()->getCouponCode() + ]; + $this->webapiTransport->write($url, $data, WebapiDecorator::PUT); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if ($response !== true) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Could not apply coupon code!'); + } + } + + /** + * Set address to quote. + * + * @param OrderInjectable $order + * @param string $addressType billing|shipping + * @return void + * @throws \Exception + */ + protected function setAddress(OrderInjectable $order, $addressType) + { + $url = $this->url . "/$addressType-address"; + if ($addressType == 'billing') { + $address = $order->getBillingAddressId(); + } else { + if (!$order->hasData('shipping_method')) { + return; + } + $address = $order->hasData('shipping_address_id') + ? $order->getShippingAddressId() + : $order->getBillingAddressId(); + } + unset($address['default_billing']); + unset($address['default_shipping']); + foreach (array_keys($this->mappingData) as $key) { + if (isset($address[$key])) { + $address[$key] = $this->mappingData[$key][$address[$key]]; + } + } + $data = ["address" => $address]; + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if (!is_numeric($response)) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception("Could not set $addressType addresss to quote!"); + } + } + + /** + * Set shipping method to quote. + * + * @param OrderInjectable $order + * @return void + * @throws \Exception + */ + protected function setShippingMethod(OrderInjectable $order) + { + if (!$order->hasData('shipping_method')) { + return; + } + $url = $this->url . '/selected-shipping-method'; + list($carrier, $method) = explode('_', $order->getShippingMethod()); + $data = [ + "carrierCode" => $carrier, + "methodCode" => $method + ]; + $this->webapiTransport->write($url, $data, WebapiDecorator::PUT); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if ($response !== true) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Could not set shipping method to quote!'); + } + } + + /** + * Set payment method to quote. + * + * @param OrderInjectable $order + * @return void + * @throws \Exception + */ + protected function setPaymentMethod(OrderInjectable $order) + { + $url = $this->url . '/selected-payment-method'; + $data = [ + "cartId" => $this->quote, + "method" => $order->getPaymentAuthExpiration() + ]; + $this->webapiTransport->write($url, $data, WebapiDecorator::PUT); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if (!is_numeric($response)) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Could not set payment method to quote!'); + } + } + + /** + * Place order. + * + * @return array + * @throws \Exception + */ + protected function placeOrder() + { + $url = $this->url . '/order'; + $data = ["cartId" => $this->quote]; + $this->webapiTransport->write($url, $data, WebapiDecorator::PUT); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + if (!is_numeric($response)) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Could not place order via web API!'); + } + + return $response; + } + + /** + * Prepare configurable product options. + * + * @param ConfigurableProduct $product + * @return array + */ + protected function prepareConfigurableOptions(ConfigurableProduct $product) + { + $options = []; + $attributesData = $product->getDataFieldConfig('configurable_attributes_data')['source']->getAttributesData(); + foreach ($product->getCheckoutData()['options']['configurable_options'] as $checkoutOption) { + $options[] = [ + 'option_id' => $attributesData[$checkoutOption['title']]['attribute_id'], + 'option_value' => $attributesData[$checkoutOption['title']]['options'][$checkoutOption['value']]['id'], + ]; + } + + return ['extension_attributes' => ['configurable_item_options' => $options]]; + } + + /** + * Prepare bundle product options. + * + * @param BundleProduct $product + * @return array + */ + protected function prepareBundleOptions(BundleProduct $product) + { + $options = []; + foreach ($product->getCheckoutData()['options']['bundle_options'] as $checkoutOption) { + foreach ($product->getBundleSelections()['bundle_options'] as $productOption) { + if (strpos($productOption['title'], $checkoutOption['title']) !== false) { + $option = []; + foreach ($productOption['assigned_products'] as $productData) { + if (strpos($productData['search_data']['name'], $checkoutOption['value']['name']) !== false) { + $qty = isset($checkoutOption['qty']) + ? $checkoutOption['qty'] + : $productData['data']['selection_qty']; + $option['option_id'] = $productData['option_id']; + $option['option_selections'][] = $productData['selection_id']; + $option['option_qty'] = $qty; + } + } + $options[] = $option; + } + } + } + + return ['extension_attributes' => ['bundle_options' => $options]]; + } + + /** + * Prepare downloadable product options. + * + * @param DownloadableProduct $product + * @return array + */ + protected function prepareDownloadableOptions(DownloadableProduct $product) + { + $checkoutData = $product->getCheckoutData(); + $links = []; + foreach ($checkoutData['options']['links'] as $link) { + $links[] = $link['id']; + } + + return ['extension_attributes' => ['downloadable_option' => ['downloadable_links' => $links]]]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable.xml index 1429cfef5546424faeae806818033ebb5e03172e..8949fe1957d60a49352cd14af40215a94a697e03 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Repository/OrderInjectable.xml @@ -221,7 +221,6 @@ <field name="store_id" xsi:type="array"> <item name="dataset" xsi:type="string">default_store_view</item> </field> - <field name="shipping_method" xsi:type="string">flatrate_flatrate</field> <field name="payment_auth_expiration" xsi:type="array"> <item name="method" xsi:type="string">checkmo</item> </field> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/PrintOrderOnFrontendStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/PrintOrderOnFrontendStep.php index a810a3ca608a2cd7927e12316d731b3b4ce47d4a..e520d538cc7570b669fe2d936fe783acc45d1e31 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/PrintOrderOnFrontendStep.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/PrintOrderOnFrontendStep.php @@ -47,7 +47,7 @@ class PrintOrderOnFrontendStep implements TestStepInterface */ public function run() { - $this->salesGuestView->getActionsToolbar()->clickLink('Print Order'); + $this->salesGuestView->getActionsToolbar()->clickLink('Print'); $this->browser->selectWindow(); } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/etc/webapi/di.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/webapi/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..cfc53a1e744c0916c115a370f8aedb8613c92c70 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/etc/webapi/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <preference for="\Magento\Sales\Test\Handler\OrderInjectable\OrderInjectableInterface" type="\Magento\Sales\Test\Handler\OrderInjectable\Webapi" /> +</config> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php index d8a6445267051487c7f813a13dc62be1c8fd6981..bbfd4495e63d6844c8b2c2b764dc614a47a12dac 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php @@ -26,7 +26,7 @@ class AssertCartPriceRuleForm extends AbstractConstraint 'actions_serialized', 'from_date', 'to_date', - 'id' + 'rule_id' ]; /** diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Fixture/SalesRule.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Fixture/SalesRule.xml index e02f275c2dfdd37e68790c5bb936cdf3704ce7d8..c82680f07a6f4751898e3ce76a81a329a100153a 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Fixture/SalesRule.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Fixture/SalesRule.xml @@ -14,7 +14,7 @@ repository_class="Magento\SalesRule\Test\Repository\SalesRule" handler_interface="Magento\SalesRule\Test\Handler\SalesRule\SalesRuleInterface" class="Magento\SalesRule\Test\Fixture\SalesRule"> - <field name="rule_id" is_required="1" /> + <field name="rule_id" is_required="0" group="null"/> <field name="name" group="rule_information" /> <field name="description" group="rule_information" /> <field name="from_date" group="rule_information" source="Magento\Backend\Test\Fixture\Source\Date" /> @@ -38,7 +38,6 @@ <field name="use_auto_generation" group="rule_information" /> <field name="uses_per_coupon" group="rule_information" /> <field name="simple_free_shipping" group="actions" /> - <field name="id" /> <field name="website_ids" group="rule_information" /> <field name="customer_group_ids" group="rule_information" /> <field name="store_labels" group="labels" /> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php index 8de3162663eb03999d0758b59ba8278e0d61f4ed..e6a910a76fa656acc36dbb993f47efb476c89d9e 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php @@ -10,12 +10,27 @@ use Magento\Backend\Test\Handler\Conditions; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Util\Protocol\CurlTransport\BackendDecorator; +use Magento\SalesRule\Test\Fixture\SalesRule; /** * Curl handler for creating sales rule. */ class Curl extends Conditions implements SalesRuleInterface { + /** + * Sales rule instance. + * + * @var SalesRule + */ + protected $fixture; + + /** + * Prepared data for request to create sales rule. + * + * @var array + */ + protected $data; + /** * Map of type parameter. * @@ -69,7 +84,7 @@ class Curl extends Conditions implements SalesRuleInterface ], 'is_rss' => [ 'Yes' => 1, - 'No' => 2, + 'No' => 0, ], 'simple_action' => [ 'Percent of product price discount' => 'by_percent', @@ -79,11 +94,11 @@ class Curl extends Conditions implements SalesRuleInterface ], 'apply_to_shipping' => [ 'Yes' => 1, - 'No' => 2, + 'No' => 0, ], 'stop_rules_processing' => [ 'Yes' => 1, - 'No' => 2, + 'No' => 0, ], 'simple_free_shipping' => [ 'No' => 0, @@ -123,23 +138,9 @@ class Curl extends Conditions implements SalesRuleInterface public function persist(FixtureInterface $fixture = null) { $this->mapTypeParams = array_merge($this->mapTypeParams, $this->additionalMapTypeParams); - $url = $_ENV['app_backend_url'] . 'sales_rule/promo_quote/save/'; - $data = $this->replaceMappingData($fixture->getData()); - $data['rule'] = []; - if (isset($data['conditions_serialized'])) { - $data['rule']['conditions'] = $this->prepareCondition($data['conditions_serialized']); - unset($data['conditions_serialized']); - } - $data['website_ids'] = $this->prepareWebsites($data); - $data['customer_group_ids'] = $this->prepareCustomerGroup($data); - - if (isset($data['actions_serialized'])) { - $this->mapTypeParams['Conditions combination']['type'] = - 'Magento\SalesRule\Model\Rule\Condition\Product\Combine'; - $data['rule']['actions'] = $this->prepareCondition($data['actions_serialized']); - unset($data['actions_serialized']); - } + $data = $this->prepareData($fixture); + $url = $_ENV['app_backend_url'] . 'sales_rule/promo_quote/save/'; $curl = new BackendDecorator(new CurlTransport(), $this->_configuration); $curl->write($url, $data); $response = $curl->read(); @@ -153,42 +154,70 @@ class Curl extends Conditions implements SalesRuleInterface throw new \Exception('Cannot find Sales Rule id'); } - return ['id' => $matches[1]]; + return ['rule_id' => $matches[1]]; + } + + /** + * Prepare data for creating sales rule request. + * + * @param FixtureInterface $fixture + * @return array + */ + public function prepareData(FixtureInterface $fixture) + { + $this->fixture = $fixture; + $this->data = $this->replaceMappingData($this->fixture->getData()); + + $this->data['rule'] = []; + if (isset($this->data['conditions_serialized'])) { + $this->data['rule']['conditions'] = $this->prepareCondition($this->data['conditions_serialized']); + unset($this->data['conditions_serialized']); + } + + $this->prepareWebsites(); + $this->prepareCustomerGroup(); + + if (isset($this->data['actions_serialized'])) { + $this->mapTypeParams['Conditions combination']['type'] = + 'Magento\SalesRule\Model\Rule\Condition\Product\Combine'; + $this->data['rule']['actions'] = $this->prepareCondition($this->data['actions_serialized']); + unset($this->data['actions_serialized']); + } + + return $this->data; } /** * Prepare website data for curl. * - * @param array $data * @return array */ - protected function prepareWebsites(array $data) + protected function prepareWebsites() { $websiteIds = []; - if (!empty($data['website_ids'])) { - foreach ($data['website_ids'] as $name) { + if (!empty($this->data['website_ids'])) { + foreach ($this->data['website_ids'] as $name) { $websiteIds[] = isset($this->websiteIds[$name]) ? $this->websiteIds[$name] : $name; } } - return $websiteIds; + $this->data['website_ids'] = $websiteIds; } /** * Prepare customer group data for curl. * - * @param array $data * @return array */ - protected function prepareCustomerGroup(array $data) + protected function prepareCustomerGroup() { $groupIds = []; - if (!empty($data['customer_group_ids'])) { - foreach ($data['customer_group_ids'] as $name) { + if (!empty($this->data['customer_group_ids'])) { + foreach ($this->data['customer_group_ids'] as $name) { $groupIds[] = isset($this->customerIds[$name]) ? $this->customerIds[$name] : $name; } } - return $groupIds; + $this->data['customer_group_ids'] = $groupIds; } } diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Webapi.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Webapi.php new file mode 100644 index 0000000000000000000000000000000000000000..66597cf0edfcdf2d94118d8217620442c2572de2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Webapi.php @@ -0,0 +1,258 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\SalesRule\Test\Handler\SalesRule; + +use Magento\Mtf\Config\DataInterface; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\System\Event\EventManagerInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; +use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; +use Magento\Mtf\Handler\Webapi as AbstractWebapi; +use Magento\SalesRule\Test\Fixture\SalesRule; + +/** + * Create new sales rule via webapi. + */ +class Webapi extends AbstractWebapi implements SalesRuleInterface +{ + /** + * Sales rule instance. + * + * @var SalesRule + */ + protected $fixture; + + /** + * Prepared data for creating sales rule. + * + * @var array + */ + protected $data; + + /** + * Curl handler instance. + * + * @var Curl + */ + protected $handlerCurl; + + /** + * List fields that only relate to coupon. + * + * @var array + */ + protected $couponFields = [ + 'coupon_code' + ]; + + + /** + * @constructor + * @param DataInterface $configuration + * @param EventManagerInterface $eventManager + * @param WebapiDecorator $webapiTransport + * @param Curl $handlerCurl + */ + public function __construct( + DataInterface $configuration, + EventManagerInterface $eventManager, + WebapiDecorator $webapiTransport, + Curl $handlerCurl + ) { + parent::__construct($configuration, $eventManager, $webapiTransport); + $this->handlerCurl = $handlerCurl; + } + + /** + * Post request for creating sales rule. + * + * @param FixtureInterface|null $fixture [optional] + * @return array + * @throws \Exception + */ + public function persist(FixtureInterface $fixture = null) + { + $data = $this->prepareData($fixture); + $url = $_ENV['app_frontend_url'] . 'rest/V1/salesRules'; + + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + + if (empty($response['rule_id'])) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('sales rule creation by webapi handler was not successful!'); + } + + $this->createCoupon($response['rule_id']); + + return ['rule_id' => $response['rule_id']]; + } + + /** + * Prepare sales rule data for webapi request. + * + * @param FixtureInterface $fixture + * @return array + */ + public function prepareData(FixtureInterface $fixture) + { + $this->fixture = $fixture; + $this->data = $this->handlerCurl->prepareData($fixture); + + $this->prepareRuleInformation(); + $this->prepareConditions(); + $this->prepareActions(); + $this->prepareLabels(); + + unset($this->data['rule']); + return ['rule' => $this->data]; + } + + /** + * Preparation of "Rule Information" tab. + * + * @return void + */ + protected function prepareRuleInformation() + { + $this->data = array_diff_key($this->data, array_flip($this->couponFields)); + $this->data['coupon_type'] = strtoupper(str_replace(' ', '_', $this->fixture->getCouponType())); + } + + /** + * Preparation of "Conditions" tab. + * + * @return void + */ + protected function prepareConditions() + { + if (isset($this->data['rule']['conditions'])) { + $this->data['condition'] = $this->convertCondition($this->data['rule']['conditions'])[0]; + } + } + + /** + * Preparation of "Actions" tab. + * + * @return void + */ + protected function prepareActions() + { + if (isset($this->data['rule']['actions'])) { + $this->data['action_condition'] = $this->convertCondition($this->data['rule']['actions'])[0]; + } + } + + /** + * Preparation of "Labels" tab. + * + * @return void + */ + protected function prepareLabels() + { + if (isset($this->data['store_labels'])) { + foreach ($this->data['store_labels'] as $storeId => $label) { + $this->data['store_labels'][$storeId] = [ + 'store_id' => $storeId, + 'store_label' => $label + ]; + } + } + } + + /** + * Create coupon related to sales rule . + * + * @param int $ruleId + * @return void + * @throws \Exception + */ + protected function createCoupon($ruleId) + { + if (!$this->fixture->hasData('coupon_code')) { + return; + } + + $url = $_ENV['app_frontend_url'] . 'rest/V1/coupons'; + $data = [ + 'coupon' => array_filter([ + 'rule_id' => $ruleId, + 'code' => $this->fixture->getCouponCode(), + 'type' => $this->data['coupon_type'], + 'usage_limit' => isset($this->data['uses_per_coupon']) + ? $this->data['uses_per_coupon'] + : null, + 'usage_per_customer' => isset($this->data['usage_per_customer']) + ? $this->data['usage_per_customer'] + : null, + 'is_primary' => true + ]) + ]; + + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + + if (empty($response['coupon_id'])) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Coupon creation by webapi handler was not successful!'); + } + } + + /** + * Convert condition data to webapi structure request. + * + * @param array $condition + * @param string $prefix [optional] + * @param int $indent [optional] + * @return array + * + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + protected function convertCondition(array $condition, $prefix = '', $indent = 1) + { + $result = []; + + $key = "{$prefix}{$indent}"; + $isContinue = isset($condition[$key]); + while ($isContinue) { + $childCondition = $this->convertCondition($condition, "{$prefix}{$indent}--", 1); + $result[] = array_filter([ + 'condition_type' => $condition[$key]['type'], + 'aggregator_type' => isset($condition[$key]['aggregator']) + ? $condition[$key]['aggregator'] + : null, + 'attribute_name' => isset($condition[$key]['attribute']) + ? $condition[$key]['attribute'] + : null, + 'operator' => isset($condition[$key]['operator']) + ? $condition[$key]['operator'] + : null, + 'value' => $condition[$key]['value'], + 'conditions' => empty($childCondition) ? null : $childCondition + ], [$this, 'filterCondition']); + + $indent += 1; + $key = "{$prefix}{$indent}"; + $isContinue = isset($condition[$key]); + } + + return $result; + } + + /** + * Filter condition data. + * + * @param mixed $var + * @return bool + */ + public function filterCondition($var) + { + return null !== $var; + } +} diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml index 67409f56197164f79354a5d9f2c2645bce598c53..4c384ea3f8853265fa0ba9877a56492a871914dc 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml @@ -11,14 +11,14 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule1 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Sales Cart Rule labels</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Sales Cart Rule labels</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> @@ -28,15 +28,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule2 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">Specific Coupon</data> <data name="salesRule/data/coupon_code" xsi:type="string">1A2B3C-456-zxc-%isolation%</data> <data name="salesRule/data/simple_action" xsi:type="string">Fixed amount discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">35</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Coupon code+fixed amount discount</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Coupon code+fixed amount discount</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">2</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> @@ -46,15 +46,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule3 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">Specific Coupon</data> <data name="salesRule/data/coupon_code" xsi:type="string">Lorem ipsum dolor sit amet, consectetur adipiscing elit - %isolation%</data> <data name="salesRule/data/simple_action" xsi:type="string">Fixed amount discount for whole cart</data> <data name="salesRule/data/discount_amount" xsi:type="string">60</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Coupon code+Fixed amount discount for whole cart</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Coupon code+Fixed amount discount for whole cart</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">3</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> @@ -64,8 +64,8 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule4 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">Specific Coupon</data> <data name="salesRule/data/coupon_code" xsi:type="string">1A2B3C-456-zxc-%isolation%</data> <data name="salesRule/data/simple_action" xsi:type="string">Buy X get Y free (discount amount is Y)</data> @@ -73,7 +73,7 @@ <data name="salesRule/data/discount_step" xsi:type="string">3</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Buy X get Y free</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Buy X get Y free</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">4</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> @@ -84,8 +84,8 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule5 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">General</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">General</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> @@ -106,14 +106,14 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule6 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">Yes</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Apply discount to Shipping Amount</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Apply discount to Shipping Amount</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <data name="productQuantity/productForSalesRule2" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> @@ -129,14 +129,14 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule7 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">For matching items only</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Free Shipping in conditions</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Free Shipping in conditions</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <data name="productQuantity/productForSalesRule2" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> @@ -148,15 +148,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule8 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/conditions_serialized" xsi:type="string">[Subtotal|is|150]</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Sales Cart Rule labels</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Sales Cart Rule labels</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <data name="productQuantity/productForSalesRule2" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> @@ -167,15 +167,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule9 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/conditions_serialized" xsi:type="string">[Subtotal|is|150]</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Subtotal Action-Condition test</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Subtotal Action-Condition test</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">2</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> @@ -185,15 +185,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule10 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/conditions_serialized" xsi:type="string">[Total Items Quantity|is|2]</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Total Items Quantity is 2</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Total Items Quantity is 2</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">2</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> @@ -203,15 +203,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule11 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/conditions_serialized" xsi:type="string">[Total Weight|is|150]</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Total Weight is 150</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Total Weight is 150</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <data name="productQuantity/productForSalesRule2" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> @@ -227,15 +227,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule12 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/conditions_serialized" xsi:type="string">{Conditions combination:[[Shipping Method|is|\[flatrate\] Fixed][Shipping Postcode|is|95814][Shipping State/Province|is|California][Shipping Country|is|United States]]}</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Rule applied conditions combination</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Rule applied conditions combination</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <data name="productQuantity/productForSalesRule2" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> @@ -246,15 +246,15 @@ <data name="salesRule/data/name" xsi:type="string">Cart Price Rule13 %isolation%</data> <data name="salesRule/data/description" xsi:type="string">Cart Price Rule Description %isolation%</data> <data name="salesRule/data/is_active" xsi:type="string">Active</data> - <data name="salesRule/data/website_ids/website_0" xsi:type="string">Main Website</data> - <data name="salesRule/data/customer_group_ids/group_0" xsi:type="string">NOT LOGGED IN</data> + <data name="salesRule/data/website_ids/0" xsi:type="string">Main Website</data> + <data name="salesRule/data/customer_group_ids/0" xsi:type="string">NOT LOGGED IN</data> <data name="salesRule/data/coupon_type" xsi:type="string">No Coupon</data> <data name="salesRule/data/conditions_serialized" xsi:type="string">{Product attribute combination:[Product Template|is|Default]}</data> <data name="salesRule/data/simple_action" xsi:type="string">Percent of product price discount</data> <data name="salesRule/data/discount_amount" xsi:type="string">50</data> <data name="salesRule/data/apply_to_shipping" xsi:type="string">No</data> <data name="salesRule/data/simple_free_shipping" xsi:type="string">No</data> - <data name="salesRule/data/store_labels/store_labels_0" xsi:type="string">Product attribute discount</data> + <data name="salesRule/data/store_labels/0" xsi:type="string">Product attribute discount</data> <data name="productQuantity/productForSalesRule1" xsi:type="string">1</data> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleSuccessSaveMessage" /> <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleForm" /> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/etc/webapi/di.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/etc/webapi/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..d925be4507a3c05b84a14345d64c236b79ae421f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/etc/webapi/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <preference for="Magento\SalesRule\Test\Handler\SalesRule\SalesRuleInterface" type="\Magento\SalesRule\Test\Handler\SalesRule\Webapi" /> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Constraint/AbstractAssertTaxRuleIsAppliedToAllPrices.php b/dev/tests/functional/tests/app/Magento/Tax/Test/Constraint/AbstractAssertTaxRuleIsAppliedToAllPrices.php index 5620cefee21a44d70c27f0d1b8348d63abd1e42f..65ed95de1547d9f2674796fae1efb829f7db7613 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/Constraint/AbstractAssertTaxRuleIsAppliedToAllPrices.php +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Constraint/AbstractAssertTaxRuleIsAppliedToAllPrices.php @@ -18,6 +18,8 @@ use Magento\Mtf\Fixture\InjectableFixture; /** * Checks that prices excl tax on category, product and cart pages are equal to specified in dataset. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ abstract class AbstractAssertTaxRuleIsAppliedToAllPrices extends AbstractConstraint { @@ -152,7 +154,6 @@ abstract class AbstractAssertTaxRuleIsAppliedToAllPrices extends AbstractConstra */ public function getCartPrices(InjectableFixture $product, $actualPrices) { - $this->checkoutCart->open(); $actualPrices['cart_item_price_excl_tax'] = $this->checkoutCart->getCartBlock()->getCartItem($product)->getPriceExclTax(); $actualPrices['cart_item_price_incl_tax'] = diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxClass/Webapi.php b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxClass/Webapi.php new file mode 100644 index 0000000000000000000000000000000000000000..e6a1ba3568948a2d07e428b47eab0caa849a76a0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxClass/Webapi.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Tax\Test\Handler\TaxClass; + +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; +use Magento\Mtf\Handler\Webapi as AbstractWebapi; + +/** + * Create Tax Class via Web API. + */ +class Webapi extends AbstractWebapi implements TaxClassInterface +{ + /** + * Persist Tax Class using Web API handler. + * + * @param FixtureInterface $fixture + * @return array + * @throws \Exception + */ + public function persist(FixtureInterface $fixture = null) + { + $data['taxClass'] = $fixture->getData(); + + $url = $_ENV['app_frontend_url'] . 'rest/V1/taxClasses'; + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + + if (!is_numeric($response)) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Tax class creation by Web API handler was not successful!'); + } + + return ['id' => $response]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Curl.php b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Curl.php index 2220821ccaa1ccbe14f2b8b5ad2ea39a97e988e8..8cb4ab4f8a99053d03ab641fdd394a7df18fe1a7 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Curl.php @@ -6,53 +6,50 @@ namespace Magento\Tax\Test\Handler\TaxRate; +use Magento\Tax\Test\Fixture\TaxRate; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Handler\Curl as AbstractCurl; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Util\Protocol\CurlTransport\BackendDecorator; /** - * Class Curl - * Curl handler for creating Tax Rate + * Curl handler for creating Tax Rate. */ class Curl extends AbstractCurl implements TaxRateInterface { /** - * Mapping for countries + * Mapping values for data. * * @var array */ - protected $countryId = [ - 'AU' => 'Australia', - 'US' => 'United States', - 'GB' => 'United Kingdom', + protected $mappingData = [ + 'tax_country_id' => [ + 'Australia' => 'AU', + 'United States' => 'US', + 'United Kingdom' => 'GB', + ], + 'tax_region_id' => [ + '*' => '0', + 'California' => '12', + 'New York' => '43', + 'Texas' => '57', + ], + 'zip_is_range' => [ + 'Yes' => '1', + 'No' => '0' + ] ]; /** - * Mapping for regions - * - * @var array - */ - protected $regionId = [ - '0' => '*', - '12' => 'California', - '43' => 'New York', - '57' => 'Texas', - ]; - - /** - * Post request for creating tax rate + * Post request for creating tax rate. * * @param FixtureInterface $fixture [optional] - * @return mixed|string + * @return array */ public function persist(FixtureInterface $fixture = null) { - $data = $fixture->getData(); - $data['tax_country_id'] = array_search($data['tax_country_id'], $this->countryId); - if (isset($data['tax_region_id'])) { - $data['tax_region_id'] = array_search($data['tax_region_id'], $this->regionId); - } + /** @var TaxRate $fixture */ + $data = $this->prepareData($fixture); $url = $_ENV['app_backend_url'] . 'tax/rate/ajaxSave/?isAjax=true'; $curl = new BackendDecorator(new CurlTransport(), $this->_configuration); @@ -65,7 +62,18 @@ class Curl extends AbstractCurl implements TaxRateInterface } /** - * Return saved tax rate id + * Prepare tax rate data. + * + * @param TaxRate $taxRate + * @return array + */ + public function prepareData(TaxRate $taxRate) + { + return $this->replaceMappingData($taxRate->getData()); + } + + /** + * Return saved tax rate id. * * @param $response * @return int|null diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Webapi.php b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Webapi.php new file mode 100644 index 0000000000000000000000000000000000000000..717e6927cc3c118faf637d485b8fae9b54724439 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRate/Webapi.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Tax\Test\Handler\TaxRate; + +use Magento\Tax\Test\Fixture\TaxRate; +use Magento\Mtf\Config\DataInterface; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; +use Magento\Mtf\Handler\Webapi as AbstractWebapi; +use Magento\Mtf\System\Event\EventManagerInterface; +use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; + +/** + * Create Tax Rate via Web API. + */ +class Webapi extends AbstractWebapi implements TaxRateInterface +{ + /** + * Tax Rate cUrl handler. + * + * @var Curl + */ + protected $taxRateCurl; + + /** + * @constructor + * @param DataInterface $configuration + * @param EventManagerInterface $eventManager + * @param WebapiDecorator $webapiTransport + * @param Curl $taxRateCurl + */ + public function __construct( + DataInterface $configuration, + EventManagerInterface $eventManager, + WebapiDecorator $webapiTransport, + Curl $taxRateCurl + ) { + parent::__construct($configuration, $eventManager, $webapiTransport); + $this->taxRateCurl = $taxRateCurl; + } + + /** + * Persist Tax Rate using Web API handler. + * + * @param FixtureInterface $fixture + * @return array + * @throws \Exception + */ + public function persist(FixtureInterface $fixture = null) + { + /** @var TaxRate $fixture */ + $data['tax_rate'] = $this->taxRateCurl->prepareData($fixture); + + $url = $_ENV['app_frontend_url'] . 'rest/V1/taxRates'; + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + + if (empty($response['id'])) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Tax rate creation by Web API handler was not successful!'); + } + + return ['id' => $response['id']]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Curl.php b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Curl.php index 1e3d68e53e40f2dd391f5acaebf7f83b643c4230..3583e38bff8a5a3b1ef2258e0e6547ca87dad059 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Curl.php @@ -6,19 +6,19 @@ namespace Magento\Tax\Test\Handler\TaxRule; +use Magento\Tax\Test\Fixture\TaxRule; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Handler\Curl as AbstractCurl; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Util\Protocol\CurlTransport\BackendDecorator; /** - * Class Curl - * Curl handler for creating Tax Rule + * Curl handler for creating Tax Rule. */ class Curl extends AbstractCurl implements TaxRuleInterface { /** - * Default Tax Class values + * Default Tax Class values. * * @var array */ @@ -28,13 +28,15 @@ class Curl extends AbstractCurl implements TaxRuleInterface ]; /** - * Post request for creating tax rule + * Post request for creating tax rule. * * @param FixtureInterface $fixture - * @return mixed|null + * @return array + * @throws \Exception */ public function persist(FixtureInterface $fixture = null) { + /** @var TaxRule $fixture */ $data = $this->prepareData($fixture); $url = $_ENV['app_backend_url'] . 'tax/rule/save/?back=1'; @@ -44,42 +46,55 @@ class Curl extends AbstractCurl implements TaxRuleInterface $response = $curl->read(); $curl->close(); + if (!strpos($response, 'data-ui-id="messages-message-success"')) { + $this->_eventManager->dispatchEvent(['curl_failed'], [$response]); + throw new \Exception("Tax rate creation by curl handler was not successful!"); + } + preg_match("~Location: [^\s]*\/rule\/(\d+)~", $response, $matches); $id = isset($matches[1]) ? $matches[1] : null; + return ['id' => $id]; } /** - * Returns data for curl POST params + * Returns data for Web API params. * - * @param FixtureInterface $fixture - * @return mixed - * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @param TaxRule $fixture + * @return array */ - protected function prepareData($fixture) + protected function prepareData(TaxRule $fixture) { $data = $fixture->getData(); - $fields = [ - 'tax_rate', - 'tax_customer_class', - 'tax_product_class', - ]; + $data = $this->prepareFieldData($fixture, $data, 'tax_rate'); + $data = $this->prepareFieldData($fixture, $data, 'tax_product_class'); + $data = $this->prepareFieldData($fixture, $data, 'tax_customer_class'); - foreach ($fields as $field) { - if (!array_key_exists($field, $data)) { - $data[$field][] = $this->defaultTaxClasses[$field]; - continue; - } - $fieldFixture = $fixture->getDataFieldConfig($field); - $fieldFixture = $fieldFixture['source']->getFixture(); - foreach ($data[$field] as $key => $value) { - $id = $fieldFixture[$key]->getId(); - if ($id === null) { - $fieldFixture[$key]->persist(); - $id = $fieldFixture[$key]->getId(); + return $data; + } + + /** + * Prepare tax rule field data using new field. + * + * @param TaxRule $fixture + * @param array $data + * @param string $fixtureField + * @param string $newField + * @return array + */ + public function prepareFieldData(TaxRule $fixture, array $data, $fixtureField, $newField = null) + { + $newField = $newField === null ? $fixtureField : $newField; + unset($data[$fixtureField]); + + if (!$fixture->hasData($fixtureField)) { + $data[$newField][] = $this->defaultTaxClasses[$fixtureField]; + } else { + foreach ($fixture->getDataFieldConfig($fixtureField)['source']->getFixture() as $taxField) { + if (!$taxField->hasData('id')) { + $taxField->persist(); } - $data[$field][$key] = $id; + $data[$newField][] = $taxField->getId(); } } diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Webapi.php b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Webapi.php new file mode 100644 index 0000000000000000000000000000000000000000..2a875d965d0336fb957ef769bc472ed4756152ad --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule/Webapi.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Tax\Test\Handler\TaxRule; + +use Magento\Tax\Test\Fixture\TaxRule; +use Magento\Mtf\Config\DataInterface; +use Magento\Mtf\Fixture\FixtureInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; +use Magento\Mtf\Handler\Webapi as AbstractWebapi; +use Magento\Mtf\System\Event\EventManagerInterface; +use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; + +/** + * Create Tax Rule via Web API handler. + */ +class Webapi extends AbstractWebapi implements TaxRuleInterface +{ + /** + * Tax Rule cUrl handler. + * + * @var Curl + */ + protected $taxRuleCurl; + + /** + * @constructor + * @param DataInterface $configuration + * @param EventManagerInterface $eventManager + * @param WebapiDecorator $webapiTransport + * @param Curl $taxRuleCurl + */ + public function __construct( + DataInterface $configuration, + EventManagerInterface $eventManager, + WebapiDecorator $webapiTransport, + Curl $taxRuleCurl + ) { + parent::__construct($configuration, $eventManager, $webapiTransport); + $this->taxRuleCurl = $taxRuleCurl; + } + + /** + * Web API request for creating Tax Rule. + * + * @param FixtureInterface $fixture + * @return array + * @throws \Exception + */ + public function persist(FixtureInterface $fixture = null) + { + /** @var TaxRule $fixture */ + $data = $this->prepareData($fixture); + + $url = $_ENV['app_frontend_url'] . 'rest/V1/taxRules'; + $this->webapiTransport->write($url, $data); + $response = json_decode($this->webapiTransport->read(), true); + $this->webapiTransport->close(); + + if (empty($response['id'])) { + $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); + throw new \Exception('Tax rule creation by Web API handler was not successful!'); + } + + return ['id' => $response['id']]; + } + + /** + * Returns data for Web API params. + * + * @param TaxRule $fixture + * @return array + */ + protected function prepareData(TaxRule $fixture) + { + $data = $fixture->getData(); + $data = $this->taxRuleCurl->prepareFieldData($fixture, $data, 'tax_rate', 'tax_rate_ids'); + $data = $this->taxRuleCurl->prepareFieldData($fixture, $data, 'tax_product_class', 'product_tax_class_ids'); + $data = $this->taxRuleCurl->prepareFieldData($fixture, $data, 'tax_customer_class', 'customer_tax_class_ids'); + + return ['rule' => $data]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRate.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRate.xml index 85abb1aa13287275ae6b077158b15cb9cd01454a..d7372e891138a93cda2ba1e3d7e71f12745cd123 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRate.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRate.xml @@ -162,5 +162,12 @@ <field name="tax_region_id" xsi:type="string">California</field> <field name="rate" xsi:type="string">30</field> </dataset> + + <dataset name="us_full_tax_rate"> + <field name="code" xsi:type="string">Tax Rate %isolation%</field> + <field name="rate" xsi:type="string">10</field> + <field name="tax_country_id" xsi:type="string">United States</field> + <field name="tax_postcode" xsi:type="string">*</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRule.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRule.xml index 42d64c06e719f5cabfd48de3c4e933215db500e6..26d4869dbaff39e325128723044e04b15d743001 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRule.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/Repository/TaxRule.xml @@ -10,7 +10,9 @@ <dataset name="default"> <field name="code" xsi:type="string">TaxIdentifier%isolation%</field> <field name="tax_rate" xsi:type="array"> - <item name="dataset" xsi:type="string">US-CA-Rate_1</item> + <item name="dataset" xsi:type="array"> + <item name="0" xsi:type="string">US-CA-Rate_1</item> + </item> </field> </dataset> @@ -175,5 +177,16 @@ <field name="priority" xsi:type="string">0</field> <field name="position" xsi:type="string">0</field> </dataset> + + <dataset name="us_full_tax_rule"> + <field name="code" xsi:type="string">TaxIdentifier%isolation%</field> + <field name="tax_rate" xsi:type="array"> + <item name="dataset" xsi:type="array"> + <item name="0" xsi:type="string">us_full_tax_rate</item> + </item> + </field> + <field name="priority" xsi:type="string">0</field> + <field name="position" xsi:type="string">0</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxCalculationTest.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxCalculationTest.xml index 6d1481e76ef1da8130ce1691829af302a393b19c..8e6e7af60a097c2d76c590ea8732e2f24f4f32ef 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxCalculationTest.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/TaxCalculationTest.xml @@ -36,64 +36,6 @@ <constraint name="Magento\Tax\Test\Constraint\AssertTaxCalculationAfterCheckoutExcludingIncludingTax" /> <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> </variation> - <variation name="TaxCalculationTestVariation2"> - <data name="description" xsi:type="string">Simple product group price with sales rule, customer tax greater than store tax and catalog price excluding tax</data> - <data name="configData" xsi:type="string">shipping_tax_class_taxable_goods, row_cat_excl_ship_incl_before_disc_on_incl, display_excluding_including_tax</data> - <data name="product" xsi:type="string">catalogProductSimple::simple_with_group_price_and_category</data> - <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> - <data name="catalogRule" xsi:type="string">-</data> - <data name="taxRule" xsi:type="string">customer_greater_store_rate</data> - <data name="customer/dataset" xsi:type="string">johndoe_unique</data> - <data name="qty" xsi:type="string">3</data> - <data name="prices/category_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/category_price_incl_tax" xsi:type="string">98.61</data> - <data name="prices/product_view_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/product_view_price_incl_tax" xsi:type="string">98.61</data> - <data name="prices/cart_item_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/cart_item_price_incl_tax" xsi:type="string">98.61</data> - <data name="prices/cart_item_subtotal_excl_tax" xsi:type="string">272.97</data> - <data name="prices/cart_item_subtotal_incl_tax" xsi:type="string">295.83</data> - <data name="prices/subtotal_excl_tax" xsi:type="string">272.97</data> - <data name="prices/subtotal_incl_tax" xsi:type="string">295.83</data> - <data name="prices/discount" xsi:type="string">147.92</data> - <data name="prices/shipping_excl_tax" xsi:type="string">13.86</data> - <data name="prices/shipping_incl_tax" xsi:type="string">15.02</data> - <data name="prices/tax" xsi:type="string">24.02</data> - <data name="prices/grand_total_excl_tax" xsi:type="string">138.91</data> - <data name="prices/grand_total_incl_tax" xsi:type="string">162.93</data> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxCalculationAfterCheckoutExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> - </variation> - <variation name="TaxCalculationTestVariation3"> - <data name="description" xsi:type="string">Simple product group price with sales rule, customer tax less than store tax and catalog price excluding tax</data> - <data name="configData" xsi:type="string">shipping_tax_class_taxable_goods, total_cat_excl_ship_incl_after_disc_on_excl, display_excluding_including_tax</data> - <data name="product" xsi:type="string">catalogProductSimple::simple_with_group_price_and_category</data> - <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> - <data name="catalogRule" xsi:type="string">-</data> - <data name="taxRule" xsi:type="string">customer_less_store_rate</data> - <data name="customer/dataset" xsi:type="string">johndoe_unique</data> - <data name="qty" xsi:type="string">3</data> - <data name="prices/category_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/category_price_incl_tax" xsi:type="string">98.50</data> - <data name="prices/product_view_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/product_view_price_incl_tax" xsi:type="string">98.50</data> - <data name="prices/cart_item_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/cart_item_price_incl_tax" xsi:type="string">98.50</data> - <data name="prices/cart_item_subtotal_excl_tax" xsi:type="string">272.97</data> - <data name="prices/cart_item_subtotal_incl_tax" xsi:type="string">295.49</data> - <data name="prices/subtotal_excl_tax" xsi:type="string">272.97</data> - <data name="prices/subtotal_incl_tax" xsi:type="string">295.49</data> - <data name="prices/discount" xsi:type="string">136.49</data> - <data name="prices/shipping_excl_tax" xsi:type="string">13.84</data> - <data name="prices/shipping_incl_tax" xsi:type="string">14.98</data> - <data name="prices/tax" xsi:type="string">12.40</data> - <data name="prices/grand_total_excl_tax" xsi:type="string">150.32</data> - <data name="prices/grand_total_incl_tax" xsi:type="string">162.72</data> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxCalculationAfterCheckoutExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> - </variation> <variation name="TaxCalculationTestVariation4"> <data name="description" xsi:type="string">Simple product special price with sales rule, customer tax less than store tax and catalog price including tax</data> <data name="configData" xsi:type="string">shipping_tax_class_taxable_goods, row_cat_incl_ship_excl_before_disc_on_incl, display_excluding_including_tax</data> @@ -181,64 +123,6 @@ <constraint name="Magento\Tax\Test\Constraint\AssertTaxCalculationAfterCheckoutExcludingIncludingTax" /> <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> </variation> - <variation name="TaxCalculationTestVariation7"> - <data name="description" xsi:type="string">Simple product group price with sales rule, customer tax equals store tax and catalog price excluding tax</data> - <data name="configData" xsi:type="string">shipping_tax_class_taxable_goods, unit_cat_excl_ship_excl_after_disc_on_excl, display_excluding_including_tax</data> - <data name="product" xsi:type="string">catalogProductSimple::simple_with_group_price_and_category</data> - <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> - <data name="catalogRule" xsi:type="string">-</data> - <data name="taxRule" xsi:type="string">customer_equals_store_rate</data> - <data name="customer/dataset" xsi:type="string">johndoe_unique</data> - <data name="qty" xsi:type="string">3</data> - <data name="prices/category_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/category_price_incl_tax" xsi:type="string">98.50</data> - <data name="prices/product_view_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/product_view_price_incl_tax" xsi:type="string">98.50</data> - <data name="prices/cart_item_price_excl_tax" xsi:type="string">90.99</data> - <data name="prices/cart_item_price_incl_tax" xsi:type="string">98.50</data> - <data name="prices/cart_item_subtotal_excl_tax" xsi:type="string">272.97</data> - <data name="prices/cart_item_subtotal_incl_tax" xsi:type="string">295.50</data> - <data name="prices/subtotal_excl_tax" xsi:type="string">272.97</data> - <data name="prices/subtotal_incl_tax" xsi:type="string">295.50</data> - <data name="prices/discount" xsi:type="string">136.49</data> - <data name="prices/shipping_excl_tax" xsi:type="string">15.00</data> - <data name="prices/shipping_incl_tax" xsi:type="string">16.24</data> - <data name="prices/tax" xsi:type="string">12.49</data> - <data name="prices/grand_total_excl_tax" xsi:type="string">151.48</data> - <data name="prices/grand_total_incl_tax" xsi:type="string">163.97</data> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxCalculationAfterCheckoutExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> - </variation> - <variation name="TaxCalculationTestVariation8"> - <data name="description" xsi:type="string">Simple product special price with sales rule, customer tax greater than store tax and catalog price including tax</data> - <data name="configData" xsi:type="string">shipping_tax_class_taxable_goods, total_cat_incl_ship_excl_before_disc_on_excl, display_excluding_including_tax</data> - <data name="product" xsi:type="string">catalogProductSimple::simple_with_group_price_and_category</data> - <data name="salesRule" xsi:type="string">active_sales_rule_for_all_groups_no_coupon</data> - <data name="catalogRule" xsi:type="string">-</data> - <data name="taxRule" xsi:type="string">customer_greater_store_rate</data> - <data name="customer/dataset" xsi:type="string">johndoe_unique</data> - <data name="qty" xsi:type="string">3</data> - <data name="prices/category_price_excl_tax" xsi:type="string">84.06</data> - <data name="prices/category_price_incl_tax" xsi:type="string">91.10</data> - <data name="prices/product_view_price_excl_tax" xsi:type="string">84.06</data> - <data name="prices/product_view_price_incl_tax" xsi:type="string">91.10</data> - <data name="prices/cart_item_price_excl_tax" xsi:type="string">84.06</data> - <data name="prices/cart_item_price_incl_tax" xsi:type="string">91.10</data> - <data name="prices/cart_item_subtotal_excl_tax" xsi:type="string">252.18</data> - <data name="prices/cart_item_subtotal_incl_tax" xsi:type="string">273.30</data> - <data name="prices/subtotal_excl_tax" xsi:type="string">252.18</data> - <data name="prices/subtotal_incl_tax" xsi:type="string">273.30</data> - <data name="prices/discount" xsi:type="string">126.09</data> - <data name="prices/shipping_excl_tax" xsi:type="string">15.00</data> - <data name="prices/shipping_incl_tax" xsi:type="string">16.26</data> - <data name="prices/tax" xsi:type="string">22.38</data> - <data name="prices/grand_total_excl_tax" xsi:type="string">141.09</data> - <data name="prices/grand_total_incl_tax" xsi:type="string">163.47</data> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxRuleIsAppliedToAllPricesExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertTaxCalculationAfterCheckoutExcludingIncludingTax" /> - <constraint name="Magento\Tax\Test\Constraint\AssertOrderTaxOnBackendExcludingIncludingTax" /> - </variation> <variation name="TaxCalculationTestVariation9"> <data name="description" xsi:type="string">Simple product tier price with sales rule, customer tax greater than store tax and catalog price excluding tax</data> <data name="configData" xsi:type="string">shipping_tax_class_taxable_goods, total_cat_excl_ship_incl_after_disc_on_incl, display_excluding_including_tax</data> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/etc/webapi/di.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/etc/webapi/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..decbd58959e191b1a9155347f98466161632e2e5 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/etc/webapi/di.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd"> + <preference for="\Magento\Tax\Test\Handler\TaxRule\TaxRuleInterface" type="\Magento\Tax\Test\Handler\TaxRule\Webapi" /> + <preference for="\Magento\Tax\Test\Handler\TaxClass\TaxClassInterface" type="\Magento\Tax\Test\Handler\TaxClass\Webapi" /> + <preference for="\Magento\Tax\Test\Handler\TaxRate\TaxRateInterface" type="\Magento\Tax\Test\Handler\TaxRate\Webapi" /> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Html/Footer.php b/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Html/Footer.php index 4781f506ae0c8482e49c7aaf1ab5113acb5c54c6..6af2b23db6f578deadf9942fa1221008d02c5f70 100644 --- a/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Html/Footer.php +++ b/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Html/Footer.php @@ -12,47 +12,54 @@ use Magento\Store\Test\Fixture\Store; /** * Footer block - * CmsIndex page footer block + * CmsIndex page Footer block */ class Footer extends Block { /** - * Link selector + * Locator value for correspondent link. * * @var string */ protected $linkSelector = '//*[contains(@class, "links")]//a[contains(text(), "%s")]'; /** - * Variable selector + * Locator value for variable. * * @var string */ protected $variableSelector = './/ul[contains(@class, "links")]/*[text()="%s"]'; /** - * Store group dropdown selector + * Locator value for "Store group" dropdown. * * @var string */ protected $storeGroupDropdown = '.switcher.store'; /** - * Store Group switch selector + * Locator value for "Store group" switcher. * * @var string */ protected $storeGroupSwitch = '[data-toggle="dropdown"]'; /** - * Store group selector + * Locator value for correspondent Store group. * * @var string */ protected $storeGroupSelector = './/a[contains(.,"%s")]'; /** - * Click on link by name + * Locator value for "Advanced Search" link. + * + * @var string + */ + protected $advancedSearchSelector = '[data-action="advanced-search"]'; + + /** + * Click on link by its title. * * @param string $linkName * @return void @@ -68,7 +75,18 @@ class Footer extends Block } /** - * Check Variable visibility by html value + * Check is link is visible. + * + * @param string $linkName + * @return bool + */ + public function isLinkVisible($linkName) + { + return $this->_rootElement->find(sprintf($this->linkSelector, $linkName), Locator::SELECTOR_XPATH)->isVisible(); + } + + /** + * Check Variable visibility by html value. * * @param string $htmlValue * @return bool @@ -82,7 +100,7 @@ class Footer extends Block } /** - * Select store group + * Select Store group. * * @param Store $store * @return void @@ -102,7 +120,7 @@ class Footer extends Block } /** - * Check if store visible in dropdown + * Check if correspondent "Store" is present in "Store" swither or not. * * @param Store $store * @return bool @@ -118,7 +136,7 @@ class Footer extends Block } /** - * Check if store group switcher is visible + * Check if "Store" switcher is visible or not. * * @return bool */ @@ -126,4 +144,14 @@ class Footer extends Block { return $this->_rootElement->find($this->storeGroupSwitch)->isVisible(); } + + /** + * Open Advanced Search. + * + * @return void + */ + public function openAdvancedSearch() + { + $this->_rootElement->find($this->advancedSearchSelector)->click(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Links.php b/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Links.php index 027b1ca6ec7fff352865e5d0a001e059f4044372..3375541068cb1264e5054958cc449dc7871bc8b9 100644 --- a/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Links.php +++ b/dev/tests/functional/tests/app/Magento/Theme/Test/Block/Links.php @@ -15,50 +15,72 @@ use Magento\Mtf\Client\Locator; class Links extends Block { /** - * Selector for qty products on compare. + * Locator value for correspondent link. * * @var string */ - protected $qtyCompareProducts = '.compare .counter.qty'; + protected $link = '//a[contains(text(), "%s")]'; /** - * Link selector. + * Locator value for welcome message. * * @var string */ - protected $link = '//a[contains(text(), "%s")]'; + protected $welcomeMessage = '.greet.welcome'; /** - * Welcome message on frontend. + * Locator value for "Expand/Collapse Customer Menu" button. * * @var string */ - protected $welcomeMessage = '.greet.welcome'; + protected $toggleButton = '[data-action="customer-menu-toggle"]'; + + /** + * Locator value for Customer Menu. + * + * @var string + */ + protected $customerMenu = '.customer-menu > ul'; /** - * Open Link by title. + * Expand Customer Menu (located in page Header) if it was collapsed. + * + * @return void + */ + protected function expandCustomerMenu() + { + $this->_rootElement->find($this->toggleButton)->click(); + } + + /** + * Open link by its title. * * @param string $linkTitle * @return void */ public function openLink($linkTitle) { - $this->_rootElement->find(sprintf($this->link, $linkTitle), Locator::SELECTOR_XPATH)->click(); + $link = $this->_rootElement->find(sprintf($this->link, $linkTitle), Locator::SELECTOR_XPATH); + if (!$link->isVisible()) { + $this->expandCustomerMenu(); + } + $link->click(); } /** - * Is visible Link by title. + * Verify if correspondent link is present or not. * * @param string $linkTitle * @return bool */ public function isLinkVisible($linkTitle) { + $this->expandCustomerMenu(); return $this->_rootElement->find(sprintf($this->link, $linkTitle), Locator::SELECTOR_XPATH)->isVisible(); } /** - * Wait for link is visible. + * Wait until correspondent link appears. * * @param string $linkTitle * @return void @@ -75,19 +97,6 @@ class Links extends Block ); } - /** - * Get the number of products added to compare list. - * - * @return string - */ - public function getQtyInCompareList() - { - $this->waitForElementVisible($this->qtyCompareProducts); - $compareProductLink = $this->_rootElement->find($this->qtyCompareProducts); - preg_match_all('/^\d+/', $compareProductLink->getText(), $matches); - return $matches[0][0]; - } - /** * Get url from link. * @@ -103,7 +112,7 @@ class Links extends Block } /** - * Waiter for welcome message. + * Wait until welcome message appears. * * @return void */ 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 11ca409773a11262b8fb03de7692e267a2849984..f8091af4cc497cfc16cbef6222ec0e7e8c00c6d1 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 @@ -88,11 +88,11 @@ class DataGrid extends Grid protected $actionButton = '.modal-inner-wrap .action-secondary'; /** - * Column header locator + * Column header locator. * * @var string */ - protected $columnHeader = "//th/span[.='%s']"; + protected $columnHeader = './/*[@data-role="grid-wrapper"]//th/span[.="%s"]'; /** * @var string 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 6af15ad6834b3f92e4814ee92a1d72f02fce3e26..6c3a76c5c99865bdc9bede513bd8ea350dd469b7 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 @@ -7,7 +7,7 @@ --> <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"> - <variation name="OnePageCheckoutTestVariation24" summary="Use UPS Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12848"> + <variation name="OnePageCheckoutUpsTestVariation1" summary="Use UPS Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12848"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">login</data> <data name="customer/dataset" xsi:type="string">default</data> @@ -22,8 +22,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty"/> </variation> - <variation name="OnePageCheckoutTestVariation25"> - <data name="description" xsi:type="string">Check Out as Guest using UPS with US shipping origin and UK customer</data> + <variation name="OnePageCheckoutUpsTestVariation2" summary="Check Out as Guest using UPS with US shipping origin and UK customer"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">guest</data> <data name="customer/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/User/Test/Repository/ConfigData.xml new file mode 100644 index 0000000000000000000000000000000000000000..8101677711e3ef9e7ccbae3d01c916df53171e39 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/User/Test/Repository/ConfigData.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2015 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/Magento/Mtf/Repository/etc/repository.xsd"> + <repository class="Magento\Config\Test\Repository\ConfigData"> + <dataset name="default_lockout_failures"> + <field name="admin/security/lockout_failures" xsi:type="array"> + <item name="scope" xsi:type="string">admin</item> + <item name="scope_id" xsi:type="number">1</item> + <item name="label" xsi:type="string">6</item> + <item name="value" xsi:type="number">6</item> + </field> + </dataset> + </repository> +</config> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.php b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d9653f0c2c7ce396086964a15b3ecd801912040c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Test\TestCase; + +use Magento\Backend\Test\Page\AdminAuthLogin; +use Magento\Config\Test\Fixture\ConfigData; +use Magento\Mtf\Fixture\FixtureFactory; +use Magento\Mtf\TestCase\Injectable; +use Magento\User\Test\Constraint\AssertUserFailedLoginMessage; +use Magento\User\Test\Fixture\User; + +/** + * Preconditions: + * 1. Create admin user. + * 2. Configure 'Maximum Login Failures to Lockout Account'. + * + * Steps: + * 1. Open Magento admin user login page. + * 2. Enter incorrect password specified number of times. + * 3. "You did not sign in correctly or your account is temporarily disabled." appears after each login attempt. + * 4. Perform all assertions. + * + * @group AuthN_&_AuthZ_(PS) + * @ZephyrId MAGETWO-12386 + */ +class LockAdminUserEntityTest extends Injectable +{ + /* tags */ + const MVP = 'no'; + const DOMAIN = 'PS'; + /* end tags */ + + /** + * Check that locked user can't log in to admin panel. + * + * @param ConfigData $config + * @param User $customAdmin + * @param string $incorrectPassword + * @param int $attempts + * @param AdminAuthLogin $adminAuth + * @param FixtureFactory $fixtureFactory + * @param AssertUserFailedLoginMessage $assertUserFailedLoginMessage + * @return array + */ + public function testUpdateAdminUser( + ConfigData $config, + User $customAdmin, + $incorrectPassword, + $attempts, + AdminAuthLogin $adminAuth, + FixtureFactory $fixtureFactory, + AssertUserFailedLoginMessage $assertUserFailedLoginMessage + ) { + // Preconditions + $config->persist(); + $customAdmin->persist(); + /** @var User $incorrectUser */ + $incorrectUser = $fixtureFactory->createByCode( + 'user', + ['data' => ['username' => $customAdmin->getUsername(), 'password' => $incorrectPassword]] + ); + + // Steps and assertions + for ($i = 0; $i < $attempts; $i++) { + $assertUserFailedLoginMessage->processAssert($adminAuth, $incorrectUser); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..5fda7b00a447b418cc11dd8edf08352d4306353e --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/LockAdminUserEntityTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2015 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\User\Test\TestCase\LockAdminUserEntityTest" summary="Prevent Locked Admin User to Log In into the Backend" ticketId="MAGETWO-12386"> + <variation name="CreateAdminUserRoleEntityTestVariation1"> + <data name="config/dataset" xsi:type="string">default_lockout_failures</data> + <data name="config/data" xsi:type="array"> + <item name="admin/security/lockout_failures" xsi:type="array"> + <item name="value" xsi:type="string">6</item> + </item> + </data> + <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> + <data name="incorrectPassword" xsi:type="string">honey boo boo</data> + <data name="attempts" xsi:type="string">7</data> + <constraint name="Magento\User\Test\Constraint\AssertUserFailedLoginMessage" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml index 692af412394d29b11c476861d27c119b054d12a1..aeac8258618545cd7c562c35b5ff2afe2af64b1d 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/NavigateMenuTest.xml @@ -7,6 +7,16 @@ --> <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"> + <variation name="NavigateMenuTest52"> + <data name="menuItem" xsi:type="string">System > Locked Users</data> + <data name="pageTitle" xsi:type="string">Locked Users</data> + <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> + </variation> + <variation name="NavigateMenuTest53"> + <data name="menuItem" xsi:type="string">System > Manage Encryption Key</data> + <data name="pageTitle" xsi:type="string">Encryption Key</data> + <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable"/> + </variation> <variation name="NavigateMenuTest92"> <data name="menuItem" xsi:type="string">System > All Users</data> <data name="pageTitle" xsi:type="string">Users</data> 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 acef9337057abceb6cf2f0715ad62315fe4b389a..7f049503781c6dd1ed7d4a5dee78fb57d79ad746 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 @@ -7,7 +7,7 @@ --> <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"> - <variation name="OnePageCheckoutTestVariation26" summary="Use USPS Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12444"> + <variation name="OnePageCheckoutUspsTestVariation1" summary="Use USPS Online Shipping Carrier on Checkout as a Registered Customer" ticketId="MAGETWO-12444"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">login</data> <data name="customer/dataset" xsi:type="string">default</data> @@ -22,8 +22,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid"/> <constraint name="Magento\Checkout\Test\Constraint\AssertCartIsEmpty"/> </variation> - <variation name="OnePageCheckoutTestVariation27"> - <data name="description" xsi:type="string">Check Out as Guest using USPS with US shipping origin and UK customer</data> + <variation name="OnePageCheckoutUspsTestVariation2" summary="Check Out as Guest using USPS with US shipping origin and UK customer"> <data name="products" xsi:type="string">catalogProductSimple::default, configurableProduct::default, bundleProduct::bundle_fixed_product</data> <data name="checkoutMethod" xsi:type="string">guest</data> <data name="customer/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCatalogCategoryLink.php b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCatalogCategoryLink.php index e887766535ed3492f5838b291c96f8c8ce0f111a..a624d4f71de5f06a941703060b9d07e08e4a9885 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCatalogCategoryLink.php +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCatalogCategoryLink.php @@ -59,7 +59,7 @@ class AssertWidgetCatalogCategoryLink extends AbstractConstraint 'Wrong category title.' ); - $cmsIndex->getSearchBlock()->clickAdvancedSearchButton(); + $cmsIndex->getFooterBlock()->openAdvancedSearch(); \PHPUnit_Framework_Assert::assertTrue( $cmsIndex->getWidgetView()->isWidgetVisible($widget, $widgetText), 'Widget with type catalog category link is absent on Advanced Search page.' diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php index 3cce4ea17193c0e3e17694a1f1062b8a4dfe0c4a..8d3170aebc9046cde9879841275c062b75b8f646 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php @@ -54,7 +54,7 @@ class AssertWidgetCmsPageLink extends AbstractConstraint 'Wrong page title on Cms page.' ); - $cmsIndex->getSearchBlock()->clickAdvancedSearchButton(); + $cmsIndex->getFooterBlock()->openAdvancedSearch(); \PHPUnit_Framework_Assert::assertTrue( $cmsIndex->getWidgetView()->isWidgetVisible($widget, $widgetText), 'Widget with type CmsPageLink is absent on Advanced Search page.' diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/Repository/Widget.xml b/dev/tests/functional/tests/app/Magento/Widget/Test/Repository/Widget.xml index c7c75cfb643531869c405862af0b92733dca5b56..a43376e87102219babd18a568469a8c36bd3526a 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/Repository/Widget.xml +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/Repository/Widget.xml @@ -10,7 +10,7 @@ <dataset name="default"> <field name="code" xsi:type="string">CMS Page Link</field> <field name="title" xsi:type="string">Cms Page Link %isolation%</field> - <field name="theme_id" xsi:type="string">Magento Blank</field> + <field name="theme_id" xsi:type="string">Magento Luma</field> <field name="store_ids" xsi:type="array"> <item name="dataset" xsi:type="string">all_store_views</item> </field> diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/CreateWidgetEntityTest.xml b/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/CreateWidgetEntityTest.xml index fdebafbc3af35b95f6dfacff69338fd25d56b4d0..c46bc78fda62b210b168c22b4d9c1b02dee53779 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/CreateWidgetEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/TestCase/CreateWidgetEntityTest.xml @@ -9,7 +9,7 @@ <testCase name="Magento\Widget\Test\TestCase\CreateWidgetEntityTest" summary="Create Widget (Frontend Apps)" ticketId="MAGETWO-27916"> <variation name="CreateWidgetEntityTestVariation1"> <data name="widget/data/code" xsi:type="string">CMS Static Block</data> - <data name="widget/data/theme_id" xsi:type="string">Magento Blank</data> + <data name="widget/data/theme_id" xsi:type="string">Magento Luma</data> <data name="widget/data/title" xsi:type="string">Title_%isolation%</data> <data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data> <data name="widget/data/widget_instance/dataset" xsi:type="string">on_category</data> @@ -20,7 +20,7 @@ </variation> <variation name="CreateWidgetEntityTestVariation2"> <data name="widget/data/code" xsi:type="string">CMS Page Link</data> - <data name="widget/data/theme_id" xsi:type="string">Magento Blank</data> + <data name="widget/data/theme_id" xsi:type="string">Magento Luma</data> <data name="widget/data/title" xsi:type="string">Title_%isolation%</data> <data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data> <data name="widget/data/widget_instance/dataset" xsi:type="string">for_cms_page_link</data> @@ -31,7 +31,7 @@ </variation> <variation name="CreateWidgetEntityTestVariation3"> <data name="widget/data/code" xsi:type="string">Recently Viewed Products</data> - <data name="widget/data/theme_id" xsi:type="string">Magento Blank</data> + <data name="widget/data/theme_id" xsi:type="string">Magento Luma</data> <data name="widget/data/title" xsi:type="string">Title_%isolation%</data> <data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data> <data name="widget/data/widget_instance/dataset" xsi:type="string">for_viewed_products</data> @@ -42,7 +42,7 @@ </variation> <variation name="CreateWidgetEntityTestVariation4"> <data name="widget/data/code" xsi:type="string">Recently Compared Products</data> - <data name="widget/data/theme_id" xsi:type="string">Magento Blank</data> + <data name="widget/data/theme_id" xsi:type="string">Magento Luma</data> <data name="widget/data/title" xsi:type="string">Title_%isolation%</data> <data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data> <data name="widget/data/widget_instance/dataset" xsi:type="string">for_compared_products</data> @@ -53,7 +53,7 @@ </variation> <variation name="CreateWidgetEntityTestVariation5"> <data name="widget/data/code" xsi:type="string">Catalog Category Link</data> - <data name="widget/data/theme_id" xsi:type="string">Magento Blank</data> + <data name="widget/data/theme_id" xsi:type="string">Magento Luma</data> <data name="widget/data/title" xsi:type="string">Title_%isolation%</data> <data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data> <data name="widget/data/widget_instance/dataset" xsi:type="string">for_category_link</data> @@ -64,7 +64,7 @@ </variation> <variation name="CreateWidgetEntityTestVariation6"> <data name="widget/data/code" xsi:type="string">Catalog Product Link</data> - <data name="widget/data/theme_id" xsi:type="string">Magento Blank</data> + <data name="widget/data/theme_id" xsi:type="string">Magento Luma</data> <data name="widget/data/title" xsi:type="string">Title_%isolation%</data> <data name="widget/data/store_ids/dataset" xsi:type="string">all_store_views</data> <data name="widget/data/widget_instance/dataset" xsi:type="string">on_product_link</data> diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php index df69f321c511977efb61b47e81358b03ec6888d7..02014d5bf7fe03e6e925825fda2af5c02e5264fb 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php @@ -11,19 +11,19 @@ use Magento\Mtf\Client\Locator; /** * Class Product - * Wishlist item product form + * Wish List item Product form */ class Product extends Form { /** - * Selector for 'Add to Cart' button + * Locator value for "Add to Cart" button. * * @var string */ protected $addToCart = '.action.tocart'; /** - * Selector for 'Remove item' button + * Locator value for "Remove item" button. * * @var string */ @@ -37,74 +37,84 @@ class Product extends Form protected $viewDetails = '.details.tooltip'; /** - * Selector for 'Details block' element + * Locator value for "Details" block. * * @var string */ protected $detailsBlock = '.product-item-tooltip'; /** - * Edit button css selector + * Locator value for "Edit" button. * * @var string */ protected $edit = '.action.edit'; /** - * Selector for option's label + * Locator value for option's label. * * @var string */ protected $optionLabel = '.tooltip.content .label'; /** - * Selector for option's value + * Locator value for option's value. * * @var string */ protected $optionValue = '.tooltip.content .values'; /** - * Selector for click on footer block + * Locator value for Footer block. * * @var string */ protected $footer = './ancestor::body//footer'; /** - * Fill item product details + * Locator value for item Price. + * + * @var string + */ + protected $price = '.price'; + + /** + * Fill item with details. * * @param array $fields * @return void */ public function fillProduct(array $fields) { + $this->hoverProductBlock(); $mapping = $this->dataMapping($fields); $this->_fill($mapping); } /** - * Click button 'Add To Cart' + * Click "Add to Cart" button. * * @return void */ public function clickAddToCart() { + $this->hoverProductBlock(); $this->_rootElement->find($this->addToCart)->click(); } /** - * Remove product from wish list + * Remove item from Wish List. * * @return void */ public function remove() { + $this->hoverProductBlock(); $this->_rootElement->find($this->remove)->click(); } /** - * Get product options + * Get Product options. * * @return array|null */ @@ -134,23 +144,35 @@ class Product extends Form } /** - * Click edit button + * Click "Edit" button. * * @return void */ public function clickEdit() { + $this->hoverProductBlock(); $this->_rootElement->find($this->edit)->click(); } /** - * Get wishlist data for the product. + * Hover Product block so that possible actions appear. + * + * @return void + */ + public function hoverProductBlock() + { + $this->_rootElement->find($this->price)->hover(); + } + + /** + * Get Wish List data for the Product. * * @param mixed $qty * @return array */ public function getWishlistData($qty = null) { + $this->hoverProductBlock(); $mapping = $this->dataMapping(); if (!is_numeric($qty)) { unset($mapping['qty']); diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertMoveProductToWishlistSuccessMessage.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertMoveProductToWishlistSuccessMessage.php index 29ef84e8349a6a0f2a998a011db4a6a95a525c40..88a588fe6309f0f7aa70c7bf380598a8effad1ed 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertMoveProductToWishlistSuccessMessage.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertMoveProductToWishlistSuccessMessage.php @@ -19,7 +19,7 @@ class AssertMoveProductToWishlistSuccessMessage extends AbstractConstraint /** * Success add message */ - const SUCCESS_MESSAGE = "%s has been moved to wish list Wish List"; + const SUCCESS_MESSAGE = "%s has been moved to your wish list."; /** * Assert that success message appears on My Wish List page after moving product to wishlist. diff --git a/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php b/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php index 0ebf3de8ec0b735bf64b4b414fa6487595217d1a..eeb5ae06c6339c5b3c0970cc9d450c6d724a0e5a 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Helper/DataTest.php @@ -7,6 +7,7 @@ namespace Magento\Backend\Helper; /** * @magentoAppArea adminhtml + * @magentoAppIsolation enabled */ class DataTest extends \PHPUnit_Framework_TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php index 3f0e20d8a744b52b81313fb8e2a38c57af8d1a0c..b8d78918bb5e700f6b01f015442780a2c501ee07 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/AuthTest.php @@ -11,6 +11,7 @@ use Magento\Framework\Exception\AuthenticationException; * Test class for \Magento\Backend\Model\Auth. * * @magentoAppArea adminhtml + * @magentoAppIsolation enabled */ class AuthTest extends \PHPUnit_Framework_TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php new file mode 100644 index 0000000000000000000000000000000000000000..0ed57577415e7f327b8de616144e2727711775b4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../../Magento/Catalog/_files/multiple_products.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$productIds = range(10, 12, 1); +foreach ($productIds as $productId) { + /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ + $stockItem = $objectManager->create('Magento\CatalogInventory\Model\Stock\Item'); + $stockItem->load($productId, 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($productId); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty(1000); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock(1); + $stockItem->save(); +} + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create('Magento\Catalog\Model\Product'); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) + ->setId(3) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Bundle Product') + ->setSku('bundle-product') + ->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]) + ->setPriceView(1) + ->setPriceType(1) + ->setPrice(10.0) + ->setBundleOptionsData( + [ + // Required "Drop-down" option + [ + 'title' => 'Option 1', + 'default_title' => 'Option 1', + 'type' => 'select', + 'required' => 1, + 'delete' => '', + ], + // Required "Radio Buttons" option + [ + 'title' => 'Option 2', + 'default_title' => 'Option 2', + 'type' => 'radio', + 'required' => 1, + 'delete' => '', + ], + // Required "Checkbox" option + [ + 'title' => 'Option 3', + 'default_title' => 'Option 3', + 'type' => 'checkbox', + 'required' => 1, + 'delete' => '', + ], + // Required "Multiple Select" option + [ + 'title' => 'Option 4', + 'default_title' => 'Option 4', + 'type' => 'multi', + 'required' => 1, + 'delete' => '', + ], + // Non-required "Multiple Select" option + [ + 'title' => 'Option 5', + 'default_title' => 'Option 5', + 'type' => 'multi', + 'required' => 0, + 'delete' => '', + ] + ] + )->setBundleSelectionsData( + [ + [ + [ + 'product_id' => 10, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 1 + ], + [ + 'product_id' => 11, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 1 + ] + ], + [ + [ + 'product_id' => 10, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 2 + ], + [ + 'product_id' => 11, + 'selection_qty' => 1, + 'selection_can_change_qty' => 1, + 'delete' => '', + 'option_id' => 2 + ] + ], + [ + [ + 'product_id' => 10, + 'selection_qty' => 1, + 'delete' => '', + 'option_id' => 3 + ], + [ + 'product_id' => 11, + 'selection_qty' => 1, + 'delete' => '', + 'option_id' => 3 + ] + ], + [ + [ + 'product_id' => 10, + 'selection_qty' => 1, + 'delete' => '', + 'option_id' => 4 + ], + [ + 'product_id' => 11, + 'selection_qty' => 1, + 'delete' => '', + 'option_id' => 4 + ] + ], + [ + [ + 'product_id' => 10, + 'selection_qty' => 1, + 'delete' => '', + 'option_id' => 5 + ], + [ + 'product_id' => 11, + 'selection_qty' => 1, + 'delete' => '', + 'option_id' => 5 + ] + ] + ] + )->save(); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..50e528f602da62214e0a0c81868f3bd23b7455a4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_multiple_options_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../../Magento/Catalog/_files/multiple_products_rollback.php'; + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); +$product->load(3); +if ($product->getId()) { + $product->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/FrontTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/FrontTest.php index 97fae046b884b8f8255be12c35281b5058f49335..c008ac20750a8856a16907ade339460fbc2cf095 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/FrontTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/FrontTest.php @@ -56,7 +56,6 @@ class FrontTest extends \PHPUnit_Framework_TestCase ['visibility'], ['url_key'], ['status'], - ['group_price'], ['price_type'], ['category_ids'], ['media_gallery'], diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/WeightTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/WeightTest.php index a1518b1efc28e3f45030994db317415ad2245ef3..7cf5a33130909b5cf2f9a0610352941ec014f9f2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/WeightTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/WeightTest.php @@ -27,7 +27,7 @@ class WeightTest extends \PHPUnit_Framework_TestCase * @param string $type * @dataProvider virtualTypesDataProvider */ - public function testIsVirtualChecked($type) + public function testProductWithoutWeight($type) { /** @var $currentProduct \Magento\Catalog\Model\Product */ $currentProduct = $this->_objectManager->create('Magento\Catalog\Model\Product'); @@ -38,10 +38,10 @@ class WeightTest extends \PHPUnit_Framework_TestCase $form->setDataObject($currentProduct); $block->setForm($form); - $this->assertContains( - 'checked="checked"', + $this->assertRegExp( + '/value="0".*checked="checked"/', $block->getElementHtml(), - 'Is Virtual checkbox is not selected for virtual products' + '"Does this have a weight" is set to "Yes" for virtual products' ); } @@ -60,7 +60,7 @@ class WeightTest extends \PHPUnit_Framework_TestCase * @param string $type * @dataProvider physicalTypesDataProvider */ - public function testIsVirtualUnchecked($type) + public function testProductHasWeight($type) { /** @var $currentProduct \Magento\Catalog\Model\Product */ $currentProduct = $this->_objectManager->create('Magento\Catalog\Model\Product'); @@ -71,11 +71,10 @@ class WeightTest extends \PHPUnit_Framework_TestCase $form = $this->_formFactory->create(); $form->setDataObject($currentProduct); $block->setForm($form); - - $this->assertNotContains( - 'checked="checked"', + $this->assertNotRegExp( + '/value="0".*checked="checked"/', $block->getElementHtml(), - 'Is Virtual checkbox is selected for physical products' + '"Does this have a weight" is set to "No" for physical products' ); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListTest.php index 078756484d66e89349eb59dde03d619f83f71bd8..84d96284b9945e52a6db58b749968bdd7fe7d4d2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListTest.php @@ -9,6 +9,7 @@ namespace Magento\Catalog\Block\Product; * Test class for \Magento\Catalog\Block\Product\List. * * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoAppArea frontend */ class ListTest extends \PHPUnit_Framework_TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/NewActionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/NewActionTest.php deleted file mode 100644 index b87d11c2c90a3ba8a11a11479bcd7f6369b460c2..0000000000000000000000000000000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/NewActionTest.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -namespace Magento\Catalog\Controller\Adminhtml\Product; - -/** - * @magentoAppArea adminhtml - */ -class NewActionTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - /** - * @TODO: Remove this test when corresponding functional tests become mandatory: - * - * Magento\Catalog\Test\TestCase\Product\CreateSimpleProductEntityTest::testCreate() variations: #5, #6. - * Magento\Catalog\Test\TestCase\Product\CreateVirtualProductEntityTest::testCreate variations: #4, #5. - * Magento\Catalog\Test\TestCase\Product\UpdateVirtualProductEntityTest::test variations: #6, #8, #10, #11. - */ - public function testCustomerGroupArePresentInGroupPriceTemplate() - { - $this->dispatch('backend/catalog/product/new/set/4/type/' . \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE); - $lines = explode(PHP_EOL, $this->getResponse()->getBody()); - foreach ($lines as $index => $line) { - if ($line && strpos($line, 'name="product[group_price][<%- data.index %>][cust_group]"') !== false) { - break; - } - } - $this->assertContains('name="product[group_price][<%- data.index %>][cust_group]"', $lines[$index]); - $this->assertContains('<option value="0">NOT LOGGED IN</option>', $lines[$index + 1]); - $this->assertContains('<option value="1">General</option>', $lines[$index + 2]); - $this->assertContains('<option value="2">Wholesale</option>', $lines[$index + 3]); - $this->assertContains('<option value="3">Retailer</option>', $lines[$index + 4]); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_with_option_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_with_option_rollback.php index 1f31bde19a817f5c4888d8fe3faee5edbf6d67be..172ba1389b391a03d3d0da01d79f83e321eb5c29 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_with_option_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_with_option_rollback.php @@ -11,7 +11,31 @@ $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Resource\Eav\Attribute' ); -$attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'attribute_with_option'); +$attribute->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'attribute_with_option'); + +/* Delete simple products per each option */ +/** @var $options \Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection */ +$options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection' +); +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +$options->setAttributeFilter($attribute->getId()); + +foreach ($options as $option) { + /** @var $product \Magento\Catalog\Model\Product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); + $product = $product->loadByAttribute('sku', 'simple_product_' . $option->getId()); + if ($product->getId()) { + $product->delete(); + } +} + if ($attribute->getId()) { $attribute->delete(); } + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/MediaTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/MediaTest.php index 0c1905c7c2c0222fcb2aa74cda455f0c7f58ce34..b257b39e36900c3ebd97e20ceeac7cb01b106d00 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/MediaTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/MediaTest.php @@ -144,10 +144,6 @@ class MediaTest extends \PHPUnit_Framework_TestCase $this->_model->beforeSave($product); $this->assertStringStartsWith('./magento_image', $product->getData('media_gallery/duplicate/100')); $this->assertEquals($fileLabel, $product->getData('image_label')); - - /* affect of beforeSave */ - $this->assertNotEquals($fileName, $this->_model->getRenamedImage($fileName)); - $this->assertEquals('test.jpg', $this->_model->getRenamedImage('test.jpg')); } public function testAfterSaveAndAfterLoad() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php index 8e9767379d7ae73930aa6848700cdca58cbcab1d..94114a39877acd67aabf1619f18868c69184d0d9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/UrlTest.php @@ -33,13 +33,6 @@ class UrlTest extends \PHPUnit_Framework_TestCase ); } - public function testGetUrlInstance() - { - $instance = $this->_model->getUrlInstance(); - $this->assertInstanceOf('Magento\Framework\Url', $instance); - $this->assertSame($instance, $this->_model->getUrlInstance()); - } - public function testGetUrlInStore() { $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_group_prices.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_group_prices.php deleted file mode 100644 index 6f8e557f6cb0c57096f649c3096499a1ea2c8357..0000000000000000000000000000000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_group_prices.php +++ /dev/null @@ -1,50 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/** @var $product \Magento\Catalog\Model\Product */ -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create('Magento\Catalog\Model\Product'); -$product->isObjectNew(true); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId(1) - ->setAttributeSetId(4) - ->setWebsiteIds([1]) - ->setName('Simple Product Group Price') - ->setSku('simple_with_group_price') - ->setPrice(10) - ->setWeight(1) - ->setShortDescription("Short description") - ->setTaxClassId(0) - ->setGroupPrice( - [ - [ - 'website_id' => 0, - 'cust_group' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID, - 'price' => 9, - ], - [ - 'website_id' => 0, - 'cust_group' => \Magento\Customer\Model\Group::CUST_GROUP_ALL, - 'price' => 7, - ], - ] - ) - ->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) - ->setCategoryIds([2]) - ->setStockData( - [ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ] - ) - ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock.php new file mode 100644 index 0000000000000000000000000000000000000000..9f5be07e7658d2f67a265dca5408150db7773d4e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL) + ->setId(21) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Virtual Product') + ->setSku('virtual-product') + ->setPrice(10) + ->setTaxClassId(0) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['is_in_stock' => 1]) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..bed07e6d07bdde76fc362f6f7eb3df351d0d2a1a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_virtual_in_stock_rollback.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); +$product->load(21); +if ($product->getId()) { + $product->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_image.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_image.php index a1c504c2c8ed1d83eac951249cda42508a8cc2f7..2d1f20d4b34399c1880f4ce4e765a8fdf9d025cd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_image.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_image.php @@ -20,5 +20,6 @@ $product->load(1) 'position' => 1, 'label' => 'Image Alt Text', 'disabled' => 0, + 'media_type' => 'image' ], ]])->save(); 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 86d5916e5446f4eeae49d6b54e81592ea0f66249..5320f53e15e11632a9a05d26050d40003ae42212 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 @@ -37,8 +37,6 @@ $productModel->setTypeId( 'simple' )->setPrice( 10 -)->setGroupPrice( - [0 => ['website_id' => 0, 'cust_group' => 0, 'price' => 9]] )->setTierPrice( [0 => ['website_id' => 0, 'cust_group' => 0, 'price_qty' => 3, 'price' => 8]] )->setVisibility( diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Model/Type/OnepageTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Model/Type/OnepageTest.php index 36d6810d4b2027e3a99f43fc1b0f7e04ccb18faf..23fe7f6b3ee75dc0caa0bea558f626786c09a45a 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Model/Type/OnepageTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Model/Type/OnepageTest.php @@ -93,7 +93,6 @@ class OnepageTest extends \PHPUnit_Framework_TestCase 'postcode' => '78757', 'country_id' => 'US', 'telephone' => '(512) 999-9999', - 'fax' => '', 'save_in_address_book' => 1, ]; $this->_model->saveShipping($data, null); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_and_options.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_and_options.php new file mode 100644 index 0000000000000000000000000000000000000000..e4cd74355fdc7cd9c182c72c57dec550f7678909 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_and_options.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../../Magento/Bundle/_files/product_with_multiple_options.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create('Magento\Catalog\Model\Product'); +$product->load(3); + +/** @var $typeInstance \Magento\Bundle\Model\Product\Type */ +//Load options +$typeInstance = $product->getTypeInstance(); +$typeInstance->setStoreFilter($product->getStoreId(), $product); +$optionCollection = $typeInstance->getOptionsCollection($product); + +$bundleOptions = []; +$bundleOptionsQty = []; +/** @var $option \Magento\Bundle\Model\Option */ +foreach ($optionCollection as $option) { + $selectionsCollection = $typeInstance->getSelectionsCollection([$option->getId()], $product); + if ($option->isMultiSelection()) { + $bundleOptions[$option->getId()] = array_column($selectionsCollection->toArray(), 'selection_id'); + } else { + $bundleOptions[$option->getId()] = $selectionsCollection->getFirstItem()->getSelectionId(); + } + $bundleOptionsQty[$option->getId()] = 1; +} + +$requestInfo = new \Magento\Framework\DataObject( + ['qty' => 1, 'bundle_option' => $bundleOptions, 'bundle_option_qty' => $bundleOptionsQty] +); + +/** @var $quote \Magento\Quote\Model\Quote $quote */ +$quote = $objectManager->create('Magento\Quote\Model\Quote'); +$quote->setStoreId(1)->setReservedOrderId('test_order_bundle')->save(); +$quote->addProduct($product, $requestInfo); +$quote->save(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_and_options_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_and_options_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..3eef4a388dbda775af35c2e4a42eda6f63710523 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_and_options_rollback.php @@ -0,0 +1,14 @@ +<?php +/** + * Rollback for quote_with_address_saved.php fixture. + * + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../Bundle/_files/product_with_multiple_options_rollback.php'; + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create('Magento\Quote\Model\Quote'); +$quote->load('test_order_bundle', 'reserved_order_id')->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Config/Model/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Config/Model/ConfigTest.php index 623db01dd154451011068f6e09d90c1097e0c6e0..09ceb5584c6b7398142d673c234958b8ac2f3381 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Model/ConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Config/Model/ConfigTest.php @@ -36,16 +36,13 @@ class ConfigTest extends \PHPUnit_Framework_TestCase /** @var $_configDataObject \Magento\Config\Model\Config */ $_configDataObject = Bootstrap::getObjectManager()->create('Magento\Config\Model\Config'); - $_configDataObject->setSection('dev')->setWebsite('base'); - - $_configData = $_configDataObject->load(); - $this->assertArrayHasKey('dev/debug/template_hints', $_configData); + $_configData = $_configDataObject->setSection('dev')->load(); + $this->assertArrayHasKey('dev/debug/template_hints_admin', $_configData); $this->assertArrayHasKey('dev/debug/template_hints_blocks', $_configData); $_configDataObject = Bootstrap::getObjectManager()->create('Magento\Config\Model\Config'); - $_configDataObject->setSection('dev'); - $_configData = $_configDataObject->load(); - $this->assertArrayNotHasKey('dev/debug/template_hints', $_configData); + $_configData = $_configDataObject->setSection('dev')->setWebsite('base')->load(); + $this->assertArrayNotHasKey('dev/debug/template_hints_admin', $_configData); $this->assertArrayNotHasKey('dev/debug/template_hints_blocks', $_configData); } diff --git a/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_groups.php b/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_groups.php index 7b2c8f02ba2759a2db8405a827508bba939d4dcd..6dca3bdf2658216af2bae0b26e6772f5070bfb95 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_groups.php +++ b/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_groups.php @@ -10,7 +10,7 @@ return [ 'restrict' => ['fields' => ['allow_ips' => ['value' => '']]], 'debug' => [ 'fields' => [ - 'template_hints' => ['value' => '0'], + 'template_hints_admin' => ['value' => '0'], 'template_hints_blocks' => ['value' => '0'], ], ], diff --git a/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_section.php b/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_section.php index c13e1e3e1b81260bda69f5aaeb86d454f2e118a8..06eb5f2789efb5006880517cbc374b9c5ec74e9d 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_section.php +++ b/dev/tests/integration/testsuite/Magento/Config/Model/_files/config_section.php @@ -17,7 +17,7 @@ return [ ], 'debug' => [ 'fields' => [ - 'template_hints' => ['value' => '1'], + 'template_hints_storefront' => ['value' => '1'], 'template_hints_blocks' => ['value' => '0'], ], ], @@ -28,7 +28,10 @@ return [ 'dev/log/file' => 'fileName.log', 'dev/log/exception_file' => 'exceptionFileName.log', ], - 'dev/debug' => ['dev/debug/template_hints' => '1', 'dev/debug/template_hints_blocks' => '0'], + 'dev/debug' => [ + 'dev/debug/template_hints_storefront' => '1', + 'dev/debug/template_hints_blocks' => '0', + ], ], ] ]; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php index dd30e5a7b95af8ddc37fc25354ec1029914fce00..ea7dfe38b513823ab7327763bd119e42bddc988e 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/Config/MatrixTest.php @@ -68,49 +68,4 @@ class MatrixTest extends \Magento\TestFramework\TestCase\AbstractBackendControll $variations ); } - - /** - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - */ - public function testAttributesMergingByGetAttributesMethod() - { - $this->_objectManager->get( - 'Magento\Framework\Registry' - )->register( - 'current_product', - $this->_objectManager->create('Magento\Catalog\Model\Product')->load(1) - ); - $this->_objectManager->get('Magento\Framework\View\LayoutInterface') - ->createBlock('Magento\Framework\View\Element\Text', 'head'); - /** @var \Magento\Catalog\Model\Entity\Attribute $usedAttribute */ - $usedAttribute = $this->_objectManager->get( - 'Magento\Catalog\Model\Entity\Attribute' - )->loadByCode( - $this->_objectManager->get('Magento\Eav\Model\Config') - ->getEntityType('catalog_product')->getId(), - 'test_configurable' - ); - /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config */ - $block = $this->_objectManager->get( - 'Magento\Framework\View\LayoutInterface' - )->createBlock( - 'Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config' - ); - $productData = [ - $usedAttribute->getId() => [ - 'label' => static::ATTRIBUTE_LABEL, - 'position' => static::ATTRIBUTE_POSITION, - ], - ]; - $this->getRequest()->setParam('product', ['configurable_attributes_data' => $productData]); - $attributes = $block->getAttributes(); - $this->assertArrayHasKey($usedAttribute->getId(), $attributes); - - $this->assertArrayHasKey('label', $attributes[$usedAttribute->getId()]); - $this->assertEquals('Test Configurable', $attributes[$usedAttribute->getId()]['label']); - - $this->assertArrayHasKey('position', $attributes[$usedAttribute->getId()]); - $this->assertEquals(0, $attributes[$usedAttribute->getId()]['position']); - } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/ConfigTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/ConfigTest.php deleted file mode 100644 index fe82e5e01ef2510826d2d93225371ae48ecfbea9..0000000000000000000000000000000000000000 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Tab/Super/ConfigTest.php +++ /dev/null @@ -1,129 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -// @codingStandardsIgnoreFile - -namespace Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super; - -use Magento\Catalog\Model\Resource\Eav\Attribute; - -/** - * @magentoAppArea adminhtml - */ -class ConfigTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - const ATTRIBUTE_LABEL = 'New Attribute Label'; - const ATTRIBUTE_POSITION = 42; - - /** - * @magentoAppIsolation enabled - */ - public function testGetSelectedAttributesForSimpleProductType() - { - $this->_objectManager->get( - 'Magento\Framework\Registry' - )->register( - 'current_product', - $this->_objectManager->create('Magento\Catalog\Model\Product') - ); - - /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config */ - $block = $this->_objectManager->get( - 'Magento\Framework\View\LayoutInterface' - )->createBlock( - 'Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config' - ); - $this->assertEquals([], $block->getSelectedAttributes()); - } - - /** - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - */ - public function testGetSelectedAttributesForConfigurableProductType() - { - $this->_objectManager->get( - 'Magento\Framework\Registry' - )->register( - 'current_product', - $this->_objectManager->create('Magento\Catalog\Model\Product')->load(1) - ); - $this->_objectManager->get('Magento\Framework\View\LayoutInterface') - ->createBlock('Magento\Framework\View\Element\Text', 'head'); - /** @var \Magento\Catalog\Model\Entity\Attribute $usedAttribute */ - $usedAttribute = $this->_objectManager->get( - 'Magento\Catalog\Model\Entity\Attribute' - )->loadByCode( - $this->_objectManager->get( - 'Magento\Eav\Model\Config' - )->getEntityType( - 'catalog_product' - )->getId(), - 'test_configurable' - ); - /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config */ - $block = $this->_objectManager->get( - 'Magento\Framework\View\LayoutInterface' - )->createBlock( - 'Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config' - ); - /** @var Attribute[] $selectedAttributes */ - $selectedAttributes = $block->getSelectedAttributes(); - $this->assertEquals([$usedAttribute->getId()], array_keys($selectedAttributes)); - /** @var Attribute $selectedAttribute */ - $selectedAttribute = reset($selectedAttributes); - $this->assertEquals('test_configurable', $selectedAttribute->getAttributeCode()); - } - - /** - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - */ - public function testAttributesMergingByGetAttributesMethod() - { - $this->_objectManager->get( - 'Magento\Framework\Registry' - )->register( - 'current_product', - $this->_objectManager->create('Magento\Catalog\Model\Product')->load(1) - ); - $this->_objectManager->get('Magento\Framework\View\LayoutInterface') - ->createBlock('Magento\Framework\View\Element\Text', 'head'); - /** @var \Magento\Catalog\Model\Entity\Attribute $usedAttribute */ - $usedAttribute = $this->_objectManager->get( - 'Magento\Catalog\Model\Entity\Attribute' - )->loadByCode( - $this->_objectManager->get( - 'Magento\Eav\Model\Config' - )->getEntityType( - 'catalog_product' - )->getId(), - 'test_configurable' - ); - /** @var $block \Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config */ - $block = $this->_objectManager->get( - 'Magento\Framework\View\LayoutInterface' - )->createBlock( - 'Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config' - ); - $productData = [ - $usedAttribute->getId() => [ - 'label' => static::ATTRIBUTE_LABEL, - 'position' => static::ATTRIBUTE_POSITION, - ], - - ]; - $this->getRequest()->setParam('product', ['configurable_attributes_data' => $productData]); - $attributes = $block->getAttributes(); - $this->assertArrayHasKey($usedAttribute->getId(), $attributes); - - $this->assertArrayHasKey('label', $attributes[$usedAttribute->getId()]); - $this->assertEquals('Test Configurable', $attributes[$usedAttribute->getId()]['label']); - - $this->assertArrayHasKey('position', $attributes[$usedAttribute->getId()]); - $this->assertEquals(0, $attributes[$usedAttribute->getId()]['position']); - } -} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/Configurable/AssociatedSelector/Backend/Grid/ColumnSetTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/Configurable/AssociatedSelector/Backend/Grid/ColumnSetTest.php deleted file mode 100644 index c3f810b5034b8c4cd4c91f76284e7a788eb50396..0000000000000000000000000000000000000000 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/Configurable/AssociatedSelector/Backend/Grid/ColumnSetTest.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ConfigurableProduct\Block\Product\Configurable\AssociatedSelector\Backend\Grid; - -class ColumnSetTest extends \PHPUnit_Framework_TestCase -{ - /** - * Testing adding column with configurable attribute to column set - * - * @magentoAppArea adminhtml - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php - */ - public function testPrepareSelect() - { - $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\Catalog\Model\Product' - ); - $product->load(1); - // fixture - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get('Magento\Framework\Registry')->register('current_product', $product); - - /** @var $layout \Magento\Framework\View\Layout */ - $layout = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - 'Magento\Framework\View\LayoutInterface' - ); - /** @var $block ColumnSet */ - $block = $layout->createBlock( - 'Magento\ConfigurableProduct\Block\Product\Configurable\AssociatedSelector\Backend\Grid\ColumnSet', - 'block' - ); - $assertBlock = $block->getLayout()->getBlock('block.test_configurable'); - $this->assertEquals('Test Configurable', $assertBlock->getHeader()); - $this->assertEquals('test_configurable', $assertBlock->getId()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Resource/Product/Collection/AssociatedProductTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Resource/Product/Collection/AssociatedProductTest.php deleted file mode 100644 index 20e4e21e4061ce7fc7911e0c2d6ab14bd56cec01..0000000000000000000000000000000000000000 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Resource/Product/Collection/AssociatedProductTest.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -/** - * Copyright © 2015 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ConfigurableProduct\Model\Resource\Product\Collection; - -class AssociatedProductTest extends \PHPUnit_Framework_TestCase -{ - /** - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/Catalog/_files/product_associated.php - */ - public function testPrepareSelect() - { - $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\Catalog\Model\Product' - ); - $product->load(1); - // fixture - $product->setId(10); - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get('Magento\Framework\Registry')->register('current_product', $product); - $collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\ConfigurableProduct\Model\Resource\Product\Collection\AssociatedProduct' - ); - $collectionProduct = $collection->getFirstItem(); - $this->assertEquals($product->getName(), $collectionProduct->getName()); - $this->assertEquals($product->getSku(), $collectionProduct->getSku()); - $this->assertEquals($product->getPrice(), $collectionProduct->getPrice()); - $this->assertEquals($product->getWeight(), $collectionProduct->getWeight()); - $this->assertEquals($product->getTypeId(), $collectionProduct->getTypeId()); - $this->assertEquals($product->getAttributeSetId(), $collectionProduct->getAttributeSetId()); - } - - /** - * @magentoAppIsolation enabled - * @magentoDataFixture Magento/Catalog/_files/product_associated.php - */ - public function testPrepareSelectForSameProduct() - { - $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\Catalog\Model\Product' - ); - $product->load(1); - // fixture - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get('Magento\Framework\Registry')->register('current_product', $product); - $collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\ConfigurableProduct\Model\Resource\Product\Collection\AssociatedProduct' - ); - $this->assertEmpty($collection->count()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable.php index 9e0d7b0d4a83d8f09143c42f66c1bdc350c8ac42..8c4e9c3ce4cfb0008bae44d1c999baa4bdbeefbc 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable.php @@ -10,82 +10,60 @@ require __DIR__ . '/configurable_attribute.php'; $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Setup\CategorySetup'); /* Create simple products per each option value*/ - /** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); $attributeValues = []; -$productIds = []; $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); +$associatedProductIds = []; $productIds = [10, 20]; array_shift($options); //remove the first option which is empty foreach ($options as $option) { /** @var $product \Magento\Catalog\Model\Product */ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); $productId = array_shift($productIds); - $product->setTypeId( - \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE - )->setId( - $productId - )->setAttributeSetId( - $attributeSetId - )->setWebsiteIds( - [1] - )->setName( - 'Configurable Option' . $option->getLabel() - )->setSku( - 'simple_' . $productId - )->setPrice( - 10 - )->setTestConfigurable( - $option->getValue() - )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE - )->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] - )->save(); + $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($productId) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Option' . $option->getLabel()) + ->setSku('simple_' . $productId) + ->setPrice(10) + ->setTestConfigurable($option->getValue()) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE) + ->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]) + ->save(); $attributeValues[] = [ 'label' => 'test', 'attribute_id' => $attribute->getId(), 'value_index' => $option->getValue(), ]; - $productIds[] = $product->getId(); + $associatedProductIds[] = $product->getId(); } /** @var $product \Magento\Catalog\Model\Product */ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); -$product->setTypeId( - \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE -)->setId( - 1 -)->setAttributeSetId( - $attributeSetId -)->setWebsiteIds( - [1] -)->setName( - 'Configurable Product' -)->setSku( - 'configurable' -)->setPrice( - 100 -)->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH -)->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED -)->setStockData( - ['use_config_manage_stock' => 1, 'is_in_stock' => 1] -)->setAssociatedProductIds( - $productIds -)->setConfigurableAttributesData( - [ +$product->setTypeId(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE) + ->setId(1) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setPrice(100) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]) + ->setAssociatedProductIds($associatedProductIds) + ->setConfigurableAttributesData( [ - 'attribute_id' => $attribute->getId(), - 'attribute_code' => $attribute->getAttributeCode(), - 'frontend_label' => 'test', - 'values' => $attributeValues, - ], - ] -)->save(); + [ + 'attribute_id' => $attribute->getId(), + 'attribute_code' => $attribute->getAttributeCode(), + 'frontend_label' => 'test', + 'values' => $attributeValues, + ], + ] + ) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_rollback.php index 9b516421a068a5bca82e00870bde8e91fa0259ff..c9de48acf5532b795582dc71ad5916d5d161b61f 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_rollback.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_rollback.php @@ -4,8 +4,10 @@ * See COPYING.txt for license details. */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Framework\Registry $registry */ -$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); +$registry = $objectManager->get('Magento\Framework\Registry'); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); @@ -16,16 +18,31 @@ $product->load(10); if ($product->getId()) { $product->delete(); } + +/** @var \Magento\CatalogInventory\Model\Stock\Status $stockStatus */ +$stockStatus = $objectManager->create('Magento\CatalogInventory\Model\Stock\Status'); +$stockStatus->load(10, 'product_id'); +$stockStatus->delete(); + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); $product->load(20); if ($product->getId()) { $product->delete(); } +/** @var \Magento\CatalogInventory\Model\Stock\Status $stockStatus */ +$stockStatus = $objectManager->create('Magento\CatalogInventory\Model\Stock\Status'); +$stockStatus->load(20, 'product_id'); +$stockStatus->delete(); + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); $product->load(1); if ($product->getId()) { $product->delete(); } +/** @var \Magento\CatalogInventory\Model\Stock\Status $stockStatus */ +$stockStatus = $objectManager->create('Magento\CatalogInventory\Model\Stock\Status'); +$stockStatus->load(1, 'product_id'); +$stockStatus->delete(); require __DIR__ . '/configurable_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php index 325fd2f467943594ed081d4673cde830b7fd2398..daa81b554db9872d75d7b92bf6b6c52419eab462 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php @@ -24,6 +24,7 @@ $requestInfo = new \Magento\Framework\DataObject( /** @var $cart \Magento\Checkout\Model\Cart */ $cart = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Checkout\Model\Cart'); $cart->addProduct($product, $requestInfo); +$cart->getQuote()->setReservedOrderId('test_cart_with_configurable'); $cart->save(); /** @var $objectManager \Magento\TestFramework\ObjectManager */ diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..a695c26857c2167834657be3d2f9226303225c0b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product_rollback.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create('Magento\Quote\Model\Quote'); +$quote->load('test_cart_with_configurable', 'reserved_order_id')->delete(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = $objectManager->create('Magento\Quote\Model\QuoteIdMask'); +$quoteIdMask->delete($quote->getId()); + +require __DIR__ . '/product_configurable_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php index d719463f60ad96888d041073905bc7092420ed74..01b7f93ad982137e8d62277d45d8eb86919275cf 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartTest.php @@ -99,7 +99,7 @@ class CartTest extends \PHPUnit_Framework_TestCase $html = $this->_block->toHtml(); $this->assertContains("<div id=\"customer_cart_grid\"", $html); $this->assertContains("<div class=\"admin__data-grid-header admin__data-grid-toolbar\"", $html); - $this->assertContains("customer_cart_gridJsObject = new varienGrid('customer_cart_grid',", $html); + $this->assertContains("customer_cart_gridJsObject = new varienGrid(\"customer_cart_grid\",", $html); $this->assertContains( "backend/customer/cart_product_composite_cart/configure/customer_id/" . self::CUSTOMER_ID_VALUE, $html diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php index 5bbdca87782a224ea4e59558e12e1231b5c26ddd..5e7361b26cfda79f70348f48503466e9d9f0b390 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php @@ -61,7 +61,7 @@ class CartsTest extends \PHPUnit_Framework_TestCase '/<div class=".*admin__data-grid-toolbar"/', $html ); - $this->assertContains("customer_cart_grid1JsObject = new varienGrid('customer_cart_grid1',", $html); + $this->assertContains("customer_cart_grid1JsObject = new varienGrid(\"customer_cart_grid1\",", $html); $this->assertContains("backend/customer/cart_product_composite_cart/configure/website_id/1", $html); } @@ -84,7 +84,7 @@ class CartsTest extends \PHPUnit_Framework_TestCase '/<div class=".*admin__data-grid-toolbar"/', $html ); - $this->assertContains("customer_cart_gridJsObject = new varienGrid('customer_cart_grid',", $html); + $this->assertContains("customer_cart_gridJsObject = new varienGrid(\"customer_cart_grid\",", $html); $this->assertContains("backend/customer/cart_product_composite_cart/configure/key/", $html); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php index c612baf883b32e7b8a9216316473f80b630cb179..13c3a9e0c26481324defc4d7191329ccdbd25d26 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php @@ -7,6 +7,9 @@ namespace Magento\Customer\Block\Widget; use Magento\TestFramework\Helper\Bootstrap; +/** + * @magentoAppArea frontend + */ class GenderTest extends \PHPUnit_Framework_TestCase { /** @var Gender */ diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/NameTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/NameTest.php index e5aea1f1e6d9a984cffc9c8e00eecdf28b1e9a9a..fa5fb5cfbc5b3d34ce45fc0dc5ef09343443a415 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/NameTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/NameTest.php @@ -9,6 +9,7 @@ use Magento\TestFramework\Helper\Bootstrap; /** * Test \Magento\Customer\Block\Widget\Name + * @magentoAppArea frontend */ class NameTest extends \PHPUnit_Framework_TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index 609c19bc96c8eb33463a18ed66b1c8e8a7a24a48..65c3aae162bf6ebbf6a8e1a66613c99bb5f8cfac 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -768,7 +768,7 @@ class AccountManagementTest extends \PHPUnit_Framework_TestCase ); /** @var \Magento\Framework\Encryption\EncryptorInterface $encryptor */ $encryptor = $this->objectManager->get('Magento\Framework\Encryption\EncryptorInterface'); - $passwordHash = $encryptor->getHash($password); + $passwordHash = $encryptor->getHash($password, true); $savedCustomer = $this->accountManagement->createAccountWithPasswordHash( $newCustomerEntity, $passwordHash diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormFactoryTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormFactoryTest.php index a2ae8c63f6a1a0f39c65bab6e8169ab5fd5a3e04..b60b036b0210d8b94a3b91dbf003d4a281f5dc3d 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormFactoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormFactoryTest.php @@ -22,7 +22,6 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase 'default_shipping' => true, 'default_billing' => false, 'company' => 'eBay Inc.', - 'fax' => '(444) 444-4444', 'middlename' => 'MiddleName', 'prefix' => 'Mr.', 'suffix' => 'Esq.', diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormTest.php index 05efaed48f125827354fbeeb2bb5933c3c6a1e97..eab6f09dffad5cd468f764d4ab5e53efff1c4cdc 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Metadata/FormTest.php @@ -39,7 +39,6 @@ class FormTest extends \PHPUnit_Framework_TestCase 'default_shipping' => 1, 'default_billing' => 0, 'company' => 'Company Name', - 'fax' => '(555) 555-5555', 'middlename' => 'Mid', 'prefix' => 'Mr.', 'suffix' => 'Esq.', @@ -57,7 +56,6 @@ class FormTest extends \PHPUnit_Framework_TestCase $requestData = [ 'company' => 'Company Name', - 'fax' => '(555) 555-5555', 'middlename' => 'Mid', 'prefix' => 'Mr.', 'suffix' => 'Esq.', diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable.php index 0873528049e242d156f5e68ecf8c41dd55646cdf..2340c819f553bf57ca48479d5938c9d67dd168c1 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable.php @@ -23,6 +23,8 @@ $product->setTypeId( \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH )->setStatus( \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED +)->setLinksPurchasedSeparately( + true )->setDownloadableData( [ 'link' => [ diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/quote_with_downloadable_product.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/quote_with_downloadable_product.php new file mode 100644 index 0000000000000000000000000000000000000000..6868fd2791947c21e22747ffc3e23d8791d7b39a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/quote_with_downloadable_product.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +require __DIR__ . '/product_downloadable.php'; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('frontend'); +$product->load(1); + +/** @var \Magento\Quote\Model\Quote $quote */ +$quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Quote\Model\Quote'); +$quote->setCustomerIsGuest( + true +)->setStoreId( + \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + 'Magento\Store\Model\StoreManagerInterface' + )->getStore()->getId() +)->setReservedOrderId( + 'reserved_order_id_1' +)->setIsMultiShipping( + false +)->addProduct( + $product, + new \Magento\Framework\DataObject([ + 'links' => array_keys($product->getDownloadableLinks()) + ]) +); +$quote->collectTotals(); +$quote->save(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Quote\Model\QuoteIdMaskFactory') + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/quote_with_downloadable_product_rollback.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/quote_with_downloadable_product_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..b86f49fe343ffe5e39aaaa3dc8d97dca5789baa6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/quote_with_downloadable_product_rollback.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create('Magento\Quote\Model\Quote'); +$quote->load('reserved_order_id_1', 'reserved_order_id')->delete(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = $objectManager->create('Magento\Quote\Model\QuoteIdMask'); +$quoteIdMask->delete($quote->getId()); +require __DIR__ . '/product_downloadable_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php index bef1e03ea68ee7d1de5d402ba7fe95ec29023ce2..731836d648132a54cfc331c66abc0876be42df44 100644 --- a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php +++ b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php @@ -254,17 +254,17 @@ class FilterTest extends \PHPUnit_Framework_TestCase 'CSS from theme' => [ TemplateTypesInterface::TYPE_HTML, 'file="css/email-1.css"', - 'color: #111;' + 'color: #111' ], 'CSS from parent theme' => [ TemplateTypesInterface::TYPE_HTML, 'file="css/email-2.css"', - 'color: #222;' + 'color: #222' ], 'CSS from grandparent theme' => [ TemplateTypesInterface::TYPE_HTML, 'file="css/email-3.css"', - 'color: #333;' + 'color: #333' ], 'Missing file parameter' => [ TemplateTypesInterface::TYPE_HTML, @@ -316,10 +316,8 @@ class FilterTest extends \PHPUnit_Framework_TestCase $this->model->setPlainTemplateMode($plainTemplateMode); $this->model->setIsChildTemplate($isChildTemplateMode); - if ($productionMode) { - $this->objectManager->get('Magento\Framework\App\State') - ->setMode(State::MODE_PRODUCTION); - } + $appMode = $productionMode ? State::MODE_PRODUCTION : State::MODE_DEVELOPER; + $this->objectManager->get('Magento\Framework\App\State')->setMode($appMode); $this->assertContains($expectedOutput, $this->model->filter($templateText)); } diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php index ad3393695ad1babf0698758d786422f9613f73a7..d66a3ebcc0a64a6c134987f2bc8ea6f751671b26 100644 --- a/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Email/Model/TemplateTest.php @@ -123,7 +123,7 @@ class TemplateTest extends \PHPUnit_Framework_TestCase ->load(); $this->setNotDefaultThemeForFixtureStore(); - $expectedViewUrl = 'static/frontend/Magento/luma/en_US/Magento_Theme/favicon.ico'; + $expectedViewUrl = 'static/frontend/Magento/blank/en_US/Magento_Theme/favicon.ico'; $this->model->setTemplateText('{{view url="Magento_Theme::favicon.ico"}}'); $this->assertStringEndsNotWith($expectedViewUrl, $this->model->getProcessedTemplate()); $this->model->setDesignConfig([ @@ -513,15 +513,18 @@ class TemplateTest extends \PHPUnit_Framework_TestCase */ protected function setNotDefaultThemeForFixtureStore() { - $theme = $this->objectManager->create('Magento\Framework\View\Design\ThemeInterface'); - $theme->load('Magento/luma', 'theme_path'); - $this->objectManager->get('Magento\Framework\App\Config\MutableScopeConfigInterface') - ->setValue( - DesignInterface::XML_PATH_THEME_ID, - $theme->getId(), - ScopeInterface::SCOPE_STORE, - 'fixturestore' - ); + $theme = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Framework\View\Design\ThemeInterface' + ); + $theme->load('Magento/blank', 'theme_path'); + \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + 'Magento\Framework\App\Config\MutableScopeConfigInterface' + )->setValue( + \Magento\Framework\View\DesignInterface::XML_PATH_THEME_ID, + $theme->getId(), + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + 'fixturestore' + ); } /** @@ -536,7 +539,7 @@ class TemplateTest extends \PHPUnit_Framework_TestCase ->load(); $this->setNotDefaultThemeForFixtureStore(); - $expectedViewUrl = 'static/frontend/Magento/luma/en_US/Magento_Theme/favicon.ico'; + $expectedViewUrl = 'static/frontend/Magento/blank/en_US/Magento_Theme/favicon.ico'; $this->model->setTemplateSubject('{{view url="Magento_Theme::favicon.ico"}}'); $this->assertStringEndsNotWith($expectedViewUrl, $this->model->getProcessedTemplateSubject([])); $this->model->setDesignConfig([ @@ -560,7 +563,7 @@ class TemplateTest extends \PHPUnit_Framework_TestCase ->load(); $this->assertStringEndsWith( - 'static/frontend/Magento/blank/en_US/Magento_Email/logo_email.png', + 'static/frontend/Magento/luma/en_US/Magento_Email/logo_email.png', $this->model->getDefaultEmailLogo() ); } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php b/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php index b81c11211dbbe55d3bfde4c02afc86c1a3a400ff..e1c319834a5c92c1e6dec8a291e817f732b1a194 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Api/ExtensionAttribute/JoinProcessorTest.php @@ -11,11 +11,16 @@ use Magento\Framework\Api\ExtensionAttribute\JoinData; use Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory; use Magento\Framework\Reflection\TypeProcessor; use Magento\Framework\App\Resource as AppResource; +use Magento\Framework\Api\ExtensionAttributesFactory; +/** + * Class to test the JoinProcessor functionality + */ class JoinProcessorTest extends \PHPUnit_Framework_TestCase { - - /** @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessor */ + /** + * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessor + */ private $joinProcessor; /** @@ -38,6 +43,16 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase */ private $appResource; + /** + * @var ExtensionAttributesFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $extensionAttributesFactory; + + /** + * @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper + */ + private $joinProcessorHelper; + protected function setUp() { $this->config = $this->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\Config') @@ -47,11 +62,10 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase ->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory') ->disableOriginalConstructor() ->getMock(); - $this->extensionAttributeJoinDataFactory = $this - ->getMockBuilder('Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory') + $this->typeProcessor = $this->getMockBuilder('Magento\Framework\Reflection\TypeProcessor') ->disableOriginalConstructor() ->getMock(); - $this->typeProcessor = $this->getMockBuilder('Magento\Framework\Reflection\TypeProcessor') + $this->extensionAttributesFactory = $this->getMockBuilder('Magento\Framework\Api\ExtensionAttributesFactory') ->disableOriginalConstructor() ->getMock(); @@ -60,13 +74,20 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase $this->appResource = $objectManager->get('Magento\Framework\App\Resource'); + $this->joinProcessorHelper = $objectManager->create( + 'Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper', + [ + 'config' => $this->config, + 'joinDataInterfaceFactory' => $this->extensionAttributeJoinDataFactory + ] + ); + $this->joinProcessor = $objectManager->create( 'Magento\Framework\Api\ExtensionAttribute\JoinProcessor', [ 'objectManager' => $objectManager, - 'config' => $this->config, - 'extensionAttributeJoinDataFactory' => $this->extensionAttributeJoinDataFactory, - 'typeProcessor' => $this->typeProcessor + 'typeProcessor' => $this->typeProcessor, + 'joinProcessorHelper' => $this->joinProcessorHelper ] ); } @@ -81,8 +102,8 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($this->getConfig())); $collection = $this->getMockBuilder('Magento\Framework\Data\Collection\AbstractDb') - ->setMethods(['joinExtensionAttribute']) ->disableOriginalConstructor() + ->setMethods(['joinExtensionAttribute']) ->getMockForAbstractClass(); $extensionAttributeJoinData = new JoinData(); @@ -112,6 +133,11 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase ); } + /** + * Will return the data that is expected from the config object + * + * @return array + */ private function getConfig() { return [ @@ -194,10 +220,17 @@ class JoinProcessorTest extends \PHPUnit_Framework_TestCase 'Magento\Framework\Api\ExtensionAttribute\Config', ['reader' => $configReader] ); + + /** @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper $extensionAttributesProcessorHelper */ + $extensionAttributesProcessorHelper = $objectManager->create( + 'Magento\Framework\Api\ExtensionAttribute\JoinProcessorHelper', + ['config' => $config] + ); + /** @var \Magento\Framework\Api\ExtensionAttribute\JoinProcessor $extensionAttributesProcessor */ $extensionAttributesProcessor = $objectManager->create( 'Magento\Framework\Api\ExtensionAttribute\JoinProcessor', - ['config' => $config] + ['joinProcessorHelper' => $extensionAttributesProcessorHelper] ); /** @var \Magento\Catalog\Model\Resource\Product\Collection $collection */ $collection = $objectManager->create('Magento\Catalog\Model\Resource\Product\Collection'); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php b/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php index f26805e1549d2407dcd468c841adabd22d32bf5a..0008a327e146f5e7835485551814d56d3a5e211b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php @@ -46,7 +46,7 @@ class ModelTest extends \PHPUnit_Framework_TestCase public function testGetValidateHash() { $password = uniqid(); - $hash = $this->_model->getHash($password); + $hash = $this->_model->getHash($password, true); $this->assertTrue(is_string($hash)); $this->assertTrue($this->_model->validateHash($password, $hash)); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute_rollback.php index 04a82aafb8219f3114aa0266fe6ef2ec24f147bb..24cf533ddf5ca4517b325491545b4a0026bb9561 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute_rollback.php @@ -14,7 +14,27 @@ $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Resource\Eav\Attribute' ); -$attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'select_attribute'); +$attribute->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'select_attribute'); + +/** @var $selectOptions \Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection */ +$selectOptions = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Eav\Model\Resource\Entity\Attribute\Option\Collection' +); +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$selectOptions->setAttributeFilter($attribute->getId()); +/* Delete simple products per each select(dropdown) option */ +foreach ($selectOptions as $option) { + /** @var $product \Magento\Catalog\Model\Product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); + $product = $product->loadByAttribute('sku', 'simple_product_' . $option->getId()); + if ($product->getId()) { + $product->delete(); + } +} if ($attribute->getId()) { $attribute->delete(); } @@ -23,3 +43,6 @@ $attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'multisel if ($attribute->getId()) { $attribute->delete(); } + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/_files/Magento/ModuleA/view/adminhtml/product/product.css b/dev/tests/integration/testsuite/Magento/Framework/View/_files/Magento/ModuleA/view/adminhtml/product/product.css index c7310b1e8793722eb99f214f44ef2088468c2135..f3774cd9f09783f88f999f848264eaeef46c9947 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/_files/Magento/ModuleA/view/adminhtml/product/product.css +++ b/dev/tests/integration/testsuite/Magento/Framework/View/_files/Magento/ModuleA/view/adminhtml/product/product.css @@ -723,8 +723,7 @@ } #product_options_container .data-table td, -#attribute-tier_price-container .data-table td, -#attribute-group_price-container .data-table td { +#attribute-tier_price-container .data-table td { vertical-align: top; } diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/Model/Resource/Product/Type/Grouped/AssociatedProductsCollectionTest.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/Model/Resource/Product/Type/Grouped/AssociatedProductsCollectionTest.php index 416e72ed185bd35179b80ab8699a8c343899bd7d..bee5e691d76d85b59bedb2c0e8df0b5b8c62368d 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedProduct/Model/Resource/Product/Type/Grouped/AssociatedProductsCollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/Model/Resource/Product/Type/Grouped/AssociatedProductsCollectionTest.php @@ -10,6 +10,7 @@ class AssociatedProductsCollectionTest extends \PHPUnit_Framework_TestCase /** * @magentoDataFixture Magento/GroupedProduct/_files/product_grouped.php * @magentoAppIsolation enabled + * @magentoDbIsolation disabled */ public function testGetColumnValues() { diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped.php index d8ac7ad7a8ce3d5b75a0a6e2e2a25fa47f06afb7..eb9f71d2f896f1f341d5351fa030fd3c759ec072 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped.php +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/_files/product_grouped.php @@ -5,7 +5,7 @@ */ require realpath(__DIR__ . '/../../') . '/Catalog/_files/product_simple_duplicated.php'; -require realpath(__DIR__ . '/../../') . '/Catalog/_files/product_virtual.php'; +require realpath(__DIR__ . '/../../') . '/Catalog/_files/product_virtual_in_stock.php'; /** @var $product \Magento\Catalog\Model\Product */ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product'); diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/catalog_product.csv b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/catalog_product.csv index 76a3e1128efd93009f7409c99c8dfc7d67cb45fd..667a2f4f215da76f8af8674bd8e769dc6b78e7a6 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/catalog_product.csv +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/_files/catalog_product.csv @@ -1,3 +1,3 @@ -sku,_store,_attribute_set,product_type,categories,_product_websites,color,cost,country_of_manufacture,created_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,description,gallery,gift_message_available,gift_wrapping_available,gift_wrapping_price,has_options,image,image_label,is_returnable,manufacturer,meta_description,meta_keyword,meta_title,minimal_price,msrp,msrp_display_actual_price_type,name,news_from_date,news_to_date,options_container,page_layout,price,quantity_and_stock_status,related_tgtr_position_behavior,related_tgtr_position_limit,required_options,short_description,small_image,small_image_label,special_from_date,special_price,special_to_date,status,tax_class_id,thumbnail,thumbnail_label,updated_at,upsell_tgtr_position_behavior,upsell_tgtr_position_limit,url_key,url_path,visibility,weight,qty,min_qty,use_config_min_qty,is_qty_decimal,backorders,use_config_backorders,min_sale_qty,use_config_min_sale_qty,max_sale_qty,use_config_max_sale_qty,is_in_stock,notify_stock_qty,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,_related_sku,_related_position,_crosssell_sku,_crosssell_position,_upsell_sku,_upsell_position,_tier_price_website,_tier_price_customer_group,_tier_price_qty,_tier_price_price,_group_price_website,_group_price_customer_group,_group_price_price,_media_attribute_id,_media_image,_media_label,_media_position,_media_is_disabled,_associated_sku,_associated_default_qty,_associated_position -"simple product 1",,Default,simple,,base,,,,"2014-12-25 19:52:47",,,,,,,,,,0,,,"Use config",,"simple product 1 ","simple product 1","simple product 1",,,,"simple product 1",,,"Block after Info Column",,100.0000,"In Stock",,,0,,,,,,,1,2,,,"2014-12-25 19:52:47",,,simple-product-1,,"catalog, search",,123.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,1,0,1,0.0000,1,0,0,1,,,,,,,,,,,,,,,,,,,,, -"simple product 2",,Default,simple,,base,,,,"2014-12-25 19:53:14",,,,,,,,,,0,,,"Use config",,"simple product 2 ","simple product 2","simple product 2",,,,"simple product 2",,,"Block after Info Column",,200.0000,"In Stock",,,0,,,,,,,1,2,,,"2014-12-25 19:53:14",,,simple-product-2,,"catalog, search",,234.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,1,0,1,0.0000,1,0,0,1,,,,,,,,,,,,,,,,,,,,, +sku,_store,_attribute_set,product_type,categories,_product_websites,color,cost,country_of_manufacture,created_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,description,gallery,gift_message_available,gift_wrapping_available,gift_wrapping_price,has_options,image,image_label,is_returnable,manufacturer,meta_description,meta_keyword,meta_title,minimal_price,msrp,msrp_display_actual_price_type,name,news_from_date,news_to_date,options_container,page_layout,price,quantity_and_stock_status,related_tgtr_position_behavior,related_tgtr_position_limit,required_options,short_description,small_image,small_image_label,special_from_date,special_price,special_to_date,status,tax_class_id,thumbnail,thumbnail_label,updated_at,upsell_tgtr_position_behavior,upsell_tgtr_position_limit,url_key,url_path,visibility,weight,qty,min_qty,use_config_min_qty,is_qty_decimal,backorders,use_config_backorders,min_sale_qty,use_config_min_sale_qty,max_sale_qty,use_config_max_sale_qty,is_in_stock,notify_stock_qty,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,_related_sku,_related_position,_crosssell_sku,_crosssell_position,_upsell_sku,_upsell_position,_tier_price_website,_tier_price_customer_group,_tier_price_qty,_tier_price_price,_media_attribute_id,_media_image,_media_label,_media_position,_media_is_disabled,_associated_sku,_associated_default_qty,_associated_position +"simple product 1",,Default,simple,,base,,,,"2014-12-25 19:52:47",,,,,,,,,,0,,,"Use config",,"simple product 1 ","simple product 1","simple product 1",,,,"simple product 1",,,"Block after Info Column",,100.0000,"In Stock",,,0,,,,,,,1,2,,,"2014-12-25 19:52:47",,,simple-product-1,,"catalog, search",,123.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,1,0,1,0.0000,1,0,0,1,,,,,,,,,,,,,,,,,, +"simple product 2",,Default,simple,,base,,,,"2014-12-25 19:53:14",,,,,,,,,,0,,,"Use config",,"simple product 2 ","simple product 2","simple product 2",,,,"simple product 2",,,"Block after Info Column",,200.0000,"In Stock",,,0,,,,,,,1,2,,,"2014-12-25 19:53:14",,,simple-product-2,,"catalog, search",,234.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,1,0,1,0.0000,1,0,0,1,,,,,,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/TemplateTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/TemplateTest.php index d501af84c4c26266dff6331a627d6ab1aad6dd30..20a8c4fb5599d23ac409d513d1d2269f86ac1a80 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/TemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/TemplateTest.php @@ -24,8 +24,8 @@ class TemplateTest extends \PHPUnit_Framework_TestCase /** * This test expects next themes for areas: - * current_store design/theme/full_name Magento/blank - * fixturestore_store design/theme/full_name Magento/luma + * current_store design/theme/full_name Magento/luma + * fixturestore_store design/theme/full_name Magento/blank * * @magentoAppIsolation enabled * @magentoAppArea adminhtml @@ -62,8 +62,8 @@ class TemplateTest extends \PHPUnit_Framework_TestCase public function getProcessedTemplateFrontendDataProvider() { return [ - 'frontend' => ['default', 'Magento/blank'], - 'frontend store' => ['fixturestore', 'Magento/luma'] + 'frontend' => ['default', 'Magento/luma'], + 'frontend store' => ['fixturestore', 'Magento/blank'] ]; } diff --git a/dev/tests/integration/testsuite/Magento/Quote/_files/empty_quote.php b/dev/tests/integration/testsuite/Magento/Quote/_files/empty_quote.php new file mode 100644 index 0000000000000000000000000000000000000000..45bd4f1f7618aa1d434c8d7d759b2cef15f0dcfc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/_files/empty_quote.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +/** @var \Magento\Quote\Model\Quote $quote */ +$quote = $objectManager->create('Magento\Quote\Model\Quote'); +$quote->setStoreId(1) + ->setIsActive(true) + ->setIsMultiShipping(false) + ->setReservedOrderId('reserved_order_id') + ->collectTotals() + ->save(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Quote\Model\QuoteIdMaskFactory') + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); diff --git a/dev/tests/integration/testsuite/Magento/Quote/_files/empty_quote_rollback.php b/dev/tests/integration/testsuite/Magento/Quote/_files/empty_quote_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..d7b3c5e66b6df27d701ae0b48510dc61edfbf0d6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/_files/empty_quote_rollback.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create('Magento\Quote\Model\Quote'); +$quote->load('reserved_order_id', 'reserved_order_id') + ->delete(); + +$objectManager->create('Magento\Quote\Model\QuoteIdMask') + ->delete($quote->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php index 80a70f023760b5eb11b95bd5402b430a4ddaf50d..79460bf822d3ef1994ed76fe90e4e2f4f4232e96 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AddressTest.php @@ -99,7 +99,6 @@ class AddressTest extends \PHPUnit_Framework_TestCase "region_id": false, "postcode": false, "telephone": false, - "fax": false, "vat_id": false }, { @@ -113,7 +112,6 @@ class AddressTest extends \PHPUnit_Framework_TestCase "region_id": false, "postcode": false, "telephone": false, - "fax": false, "vat_id": false }, { @@ -127,7 +125,6 @@ class AddressTest extends \PHPUnit_Framework_TestCase "region_id": false, "postcode": false, "telephone": false, - "fax": false, "vat_id": false } ]'; diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php index 4b97d4df5f8ec8ac83175775f35ce1c010f80533..94a499e86e149ad063e178a091a964a6e2ae63c2 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php @@ -71,12 +71,12 @@ class FormTest extends \PHPUnit_Framework_TestCase {"firstname":"John","lastname":"Smith","company":false,"street":"Green str, 67","city":"CityM", "country_id":"US", "region":"Alabama","region_id":1, - "postcode":"75477","telephone":"3468676","fax":false,"vat_id":false}, + "postcode":"75477","telephone":"3468676","vat_id":false}, "{$addressIds[1]}": {"firstname":"John","lastname":"Smith","company":false,"street":"Black str, 48","city":"CityX", "country_id":"US", "region":"Alabama","region_id":1, - "postcode":"47676","telephone":"3234676","fax":false,"vat_id":false} + "postcode":"47676","telephone":"3234676","vat_id":false} }, "store_id":1,"currency_symbol":"$","shipping_method_reseted":true,"payment_method":null } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/TotalsTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/TotalsTest.php index 14d39817dec02dc1aa4c1304d2548827a2bf92ba..35e1a7dfc8d21e43fc9eb90dbca9afd8e02dd386 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/TotalsTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/TotalsTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Sales\Block\Order; +/** + * @magentoAppArea frontend + */ class TotalsTest extends \PHPUnit_Framework_TestCase { public function testToHtmlChildrenInitialized() diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/ShipmentSenderTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/ShipmentSenderTest.php index 1014c56087499dc1cacb3fbb86c3edbac5df6fd7..1588c0a838047247e301f0aefc80595538e271a9 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/ShipmentSenderTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Email/Sender/ShipmentSenderTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Sales\Model\Order\Email\Sender; +/** + * @magentoAppArea frontend + */ class ShipmentSenderTest extends \PHPUnit_Framework_TestCase { /** diff --git a/dev/tests/integration/testsuite/Magento/Store/Controller/Store/SwitchActionTest.php b/dev/tests/integration/testsuite/Magento/Store/Controller/Store/SwitchActionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..00705fdf255ebee78acdc2f266c34cead2d5bc06 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/Controller/Store/SwitchActionTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Store\Controller\Store; + +/** + * Test for store switch controller. + */ +class SwitchActionTest extends \Magento\TestFramework\TestCase\AbstractController +{ + /** + * Ensure that proper default store code is calculated. + * + * Make sure that if default store code is changed from 'default' to something else, + * proper code is used in HTTP context. If default store code is still 'default' this may lead to + * incorrect work of page cache. + * + * @magentoDbIsolation enabled + */ + public function testExecuteWithCustomDefaultStore() + { + + $defaultStoreCode = 'default'; + $modifiedDefaultCode = 'modified_default_code'; + $this->changeStoreCode($defaultStoreCode, $modifiedDefaultCode); + + $this->dispatch('stores/store/switch'); + /** @var \Magento\Framework\App\Http\Context $httpContext */ + $httpContext = $this->_objectManager->get('Magento\Framework\App\Http\Context'); + $httpContext->unsValue(\Magento\Store\Model\Store::ENTITY); + $this->assertEquals($modifiedDefaultCode, $httpContext->getValue(\Magento\Store\Model\Store::ENTITY)); + + $this->changeStoreCode($modifiedDefaultCode, $defaultStoreCode); + } + + /** + * Change store code. + * + * @param string $from + * @param string $to + */ + protected function changeStoreCode($from, $to) + { + /** @var \Magento\Store\Model\Store $store */ + $store = $this->_objectManager->create('Magento\Store\Model\Store'); + $store->load($from, 'code'); + $store->setCode($to); + $store->save(); + /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ + $storeManager = $this->_objectManager->get('Magento\Store\Model\StoreManagerInterface'); + $storeManager->reinitStores(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php b/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php index 38c0d46aae2e4971ab47f0026e6b5df0447bc868..1c8ed4acb2d3bd9359147653798f0177f5d60379 100644 --- a/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php +++ b/dev/tests/integration/testsuite/Magento/Test/Integrity/Modular/LayoutFilesTest.php @@ -88,10 +88,6 @@ class LayoutFilesTest extends \PHPUnit_Framework_TestCase $typeAttr => 'object', 'value' => 'Magento\GroupedProduct\Model\Resource\Product\Type\Grouped\AssociatedProductsCollection', ], - [ - $typeAttr => 'object', - 'value' => 'Magento\ConfigurableProduct\Model\Resource\Product\Collection\AssociatedProduct' - ], [$typeAttr => 'object', 'value' => 'Magento\Solr\Model\Resource\Search\Grid\Collection'], [$typeAttr => 'object', 'value' => 'Magento\Wishlist\Model\Resource\Item\Collection\Grid'], [ diff --git a/dev/tests/integration/testsuite/Magento/Theme/Block/Html/BreadcrumbsTest.php b/dev/tests/integration/testsuite/Magento/Theme/Block/Html/BreadcrumbsTest.php index 594369188dd1ab65a78ac26f6a6b340adff0be24..8a315025436d82696a8bd1497415d1f2850dd053 100644 --- a/dev/tests/integration/testsuite/Magento/Theme/Block/Html/BreadcrumbsTest.php +++ b/dev/tests/integration/testsuite/Magento/Theme/Block/Html/BreadcrumbsTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Theme\Block\Html; +/** + * @magentoAppArea frontend + */ class BreadcrumbsTest extends \PHPUnit_Framework_TestCase { /** diff --git a/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php b/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php index bd8394313fe59e47c1fe3ba8fad31964dfad937e..1da20e195ac5ae4f2080f7d75032a919912a776b 100644 --- a/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Model/UserTest.php @@ -329,7 +329,7 @@ class UserTest extends \PHPUnit_Framework_TestCase $this->_model->save(); $this->assertNotContains('123123q', $this->_model->getPassword(), 'Password is expected to be hashed'); $this->assertRegExp( - '/^[0-9a-f]+:[0-9a-zA-Z]{32}$/', + '/^[0-9a-f]+:[0-9a-zA-Z]{32}:[0-9]+$/', $this->_model->getPassword(), 'Salt is expected to be saved along with the password' ); diff --git a/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php b/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php index f9f6ceda847cd08e413b946714077ae302d69e57..d2d01e2e0e9d3b980c943e3a53c757e531486679 100644 --- a/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Webapi/Controller/PathProcessorTest.php @@ -22,6 +22,7 @@ class PathProcessorTest extends \PHPUnit_Framework_TestCase { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->storeManager = $objectManager->get('Magento\Store\Model\StoreManagerInterface'); + $this->storeManager->reinitStores(); $this->pathProcessor = $objectManager->get('Magento\Webapi\Controller\PathProcessor'); } diff --git a/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php b/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php index 895132b55846cecf1abd3357ebe5b88fdabee1e5..5b8e110b28ecf72d05343ff7c71e5ea0d1617f24 100644 --- a/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php +++ b/dev/tests/integration/testsuite/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/ContainerTest.php @@ -39,10 +39,8 @@ class ContainerTest extends \PHPUnit_Framework_TestCase */ public function testAvailableContainers() { - $design = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - 'Magento\Framework\View\DesignInterface' - ); - $this->block->setTheme($design->getDesignTheme()->getId()); + $themeId = '3'; + $this->block->setTheme($themeId); $this->assertContains('<option value="before.body.end" >', $this->block->toHtml()); } } diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7457a325e17e4f8476a869e71334a020c341363c --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js @@ -0,0 +1,37 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Ui/js/grid/editing/bulk' +], function (Bulk) { + 'use strict'; + + describe('Magento_Ui/js/grid/editing/bulk', function () { + var bulkObj, + temp; + + beforeEach(function () { + bulkObj = new Bulk(); + }); + it('has initObservable', function () { + expect(bulkObj).toBeDefined(); + }); + it('has apply method', function () { + spyOn(bulkObj, 'isValid'); + temp = bulkObj.apply(); + expect(bulkObj.isValid).toHaveBeenCalled(); + expect(temp).toBeDefined(); + }); + it('can apply data', function () { + spyOn(bulkObj, 'getData'); + bulkObj.applyData(); + expect(bulkObj.getData).toHaveBeenCalled(); + }); + it('has updateState method', function () { + spyOn(bulkObj, 'updateState'); + bulkObj.updateState(); + expect(bulkObj.updateState).toHaveBeenCalled(); + }); + }) +}); \ No newline at end of file diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js index 71cc27d7da04d57d3a572107e450d290cc4e83e0..4df1017646af7dc8846129ccc4eece119cc291b4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js @@ -4,177 +4,80 @@ */ define([ - 'underscore', 'Magento_Ui/js/grid/filters/filters' -], function (_, Filters) { +], function (Filter) { 'use strict'; - describe('ui/js/grid/filters/filters', function () { - var filters; + describe('Magento_Ui/js/grid/filters/filters', function () { + var filterObj, + temp; beforeEach(function () { - filters = new Filters({ - elems: [], - index: 'index', - name: 'name', - indexField: 'id', - dataScope: 'scope', - provider: 'provider' - }); + filterObj = new Filter(); }); - - it('Default state - Select no fields.', function () { - expect(filters.elems()).toEqual([]); - filters.elems.push({id:1}); - filters.elems.push({id:1}); - expect(filters.elems()).not.toEqual([]); + it('has been initialized', function () { + expect(filterObj).toBeDefined(); }); - it('Checks if specified filter is active.', function () { - var filter = {id: 1}; - - expect(filters.isFilterActive(filter)).toBe(false); - filters.active().push(filter); - expect(filters.isFilterActive(filter)).toBe(true); + it('has initObservable', function () { + temp = filterObj.initObservable(); + expect(temp).toBeDefined(); }); - it('Tells whether specified filter should be visible.', function () { - var filter = { - visible: function () { - return false; - } - }; - - expect(filters.isFilterVisible(filter)).toBe(false); - filters.active().push(filter); - expect(filters.isFilterActive(filter)).toBe(true); - filter.visible = function() { - return true; - }; - expect(filters.isFilterActive(filter)).toBe(true); - filters.active().pop(); - expect(filters.isFilterActive(filter)).toBe(false); + it('has initElement', function () { + spyOn(filterObj, 'initElement'); + filterObj.initElement(); + expect(filterObj.initElement).toHaveBeenCalled(); }); - it('Checks if collection has visible filters.', function () { - var filter = { - visible: function () { - return false; - } + it('has clear', function () { + temp = filterObj.clear(); + expect(temp).toBeDefined(); + }); + it('has apply', function () { + temp = filterObj.apply(); + expect(temp).toBeDefined(); + }); + it('has cancel', function () { + temp = filterObj.cancel(); + expect(temp).toBeDefined(); + }); + it('has isOpened method', function () { + filterObj.opened = function () { + return true; }; - - filters.elems.push(filter); - expect(filters.hasVisible()).toBe(false); - filter.visible = function() { + filterObj.hasVisible = function () { return true; }; - filters.elems.push(filter); - expect(filters.hasVisible()).toBe(true); - filters.elems.removeAll(); - expect(filters.hasVisible()).toBe(false); - filters.active().push(filter); - expect(filters.hasVisible()).toBe(false); + temp = filterObj.isOpened(); + expect(temp).toBeTruthy(); }); - it('Tells whether filters panel should be opened.', function () { - var filter = { + it('has isFilterVisible method', function () { + temp = { visible: function () { return false; } }; - - filters.opened(false); - filters.elems.push(filter); - expect(filters.isOpened()).toBe(false); - filter.visible = function() { - return true; - }; - filters.elems.push(filter); - filters.opened(true); - expect(filters.isOpened()).toBe(true); - filters.elems.removeAll(); - expect(filters.isOpened()).toBe(false); - filters.active().push(filter); - expect(filters.isOpened()).toBe(false); - }); - it('Resets filters to the last applied state.', function () { - filters.applied = {}; - filters.filters = {}; - filters.cancel(); - expect(filters.filters).toEqual(filters.filters); - filters.filters = {id:1}; - filters.cancel(); - expect(filters.filters).toEqual({}); - filters.applied = {id:1}; - filters.cancel(); - expect(filters.filters).toEqual(filters.applied); + spyOn(filterObj, 'isFilterActive'); + filterObj.isFilterVisible(temp); + expect(filterObj.isFilterActive).toHaveBeenCalled(); }); - it('Sets filters data to the applied state.', function () { - filters.applied = {}; - filters.filters = {}; - filters.apply(); - expect(filters.applied).toEqual({}); - filters.filters = {}; - filters.applied = {id:2}; - filters.apply(); - expect(filters.applied).toEqual({}); - filters.filters = {id:1}; - filters.applied = {}; - filters.apply(); - expect(filters.applied).toEqual({id:1}); - filters.filters = {id:1}; - filters.applied = {id:2}; - filters.apply(); - expect(filters.applied).toEqual({id:1}); + it('has isFilterActive method', function () { + spyOn(filterObj, 'isFilterActive'); + filterObj.isFilterActive(); + expect(filterObj.isFilterActive).toHaveBeenCalled(); }); - it('Clears filters data.', function () { - var elem = { - value: '', - getPreview: function () { - return true; - }, - clear: function () { - this.value = ''; - return this.value; - } - }; - - filters.active.push(elem); - filters.applied = {}; - filters.filters = {}; - filters.clear(); - expect(filters.active.first().value).toEqual(''); - filters.active.first().value = 1; - filters.clear(); - expect(filters.active.first().value).toEqual(''); + it('has hasVisible method', function () { + spyOn(filterObj, 'hasVisible'); + filterObj.hasVisible(); + expect(filterObj.hasVisible).toHaveBeenCalled(); }); - it('Set active elements where exist value from elems.', function () { - var elem = { - getPreview: function () { - return true; - }, - hasData: function () { - return false; - } - }; - - filters.elems.push(elem); - filters.extractActive(); - expect(filters.active().length).toEqual(0); - elem.hasData = function() { - return true; - }; - filters.elems.removeAll(); - filters.elems().push(elem); - filters.extractActive(); - expect(filters.active().length).toEqual(1); + it('has extractActive method', function () { + spyOn(filterObj, 'extractActive'); + filterObj.extractActive(); + expect(filterObj.extractActive).toHaveBeenCalled(); }); - it('Set previews from argument elements.', function () { - var elem = { - getPreview: function() { - return true; - } - }; - - filters.elems.push(elem); - filters.extractActive(filters.elems); - expect(filters.active().length).toEqual(0); + it('has extractPreviews method', function () { + spyOn(filterObj, 'extractPreviews'); + filterObj.extractPreviews(); + expect(filterObj.extractPreviews).toHaveBeenCalled(); }); }); -}); +}); \ No newline at end of file diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js index 7a03ca73938611fdc2f935c6d9afd3849c54f752..a213081a8115aa179513df062d8c1b424db64ecf 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js @@ -51,8 +51,9 @@ define([ expect(type).toEqual('object'); }); it('Check "this.table" variable', function () { - obj.initTable('magento'); - expect(obj.table).toEqual('magento'); + arg = document.createElement('table'); + obj.initTable(arg); + expect(arg.classList.contains(obj.fixedLayoutClass)).toBeTruthy(); }); }); describe('"initColumn" method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d1b31419379e51f76c853263dd72cc7cc0d32d6c --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js @@ -0,0 +1,57 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_Ui/js/grid/search/search' +], function (Search) { + 'use strict'; + + describe('Magento_Ui/js/search/search', function () { + var searchObj, + temp; + + beforeEach(function () { + searchObj = new Search(); + }); + it('has initialized', function () { + expect(searchObj).toBeDefined(); + }); + it('has initObservable', function () { + temp = searchObj.initObservable(); + expect(temp).toBeDefined(); + }); + it('has initObservable', function () { + spyOn(searchObj, 'initChips'); + searchObj.initChips(); + expect(searchObj.initChips).toHaveBeenCalled(); + }); + it('has initChips', function () { + spyOn(searchObj, 'chips'); + searchObj.initChips(); + expect(searchObj.chips).toHaveBeenCalled(); + }); + it('has clear', function () { + spyOn(searchObj, 'value'); + searchObj.clear(); + expect(searchObj.value).toHaveBeenCalled(); + }); + it('has clear', function () { + spyOn(searchObj, 'inputValue'); + searchObj.cancel(); + expect(searchObj.inputValue).toHaveBeenCalled(); + }); + it('has apply', function () { + spyOn(searchObj, 'value'); + spyOn(searchObj, 'inputValue'); + searchObj.apply(); + expect(searchObj.value).toHaveBeenCalled(); + expect(searchObj.inputValue).toHaveBeenCalled(); + }); + it('has updatePreview', function () { + spyOn(searchObj, 'updatePreview'); + searchObj.updatePreview(); + expect(searchObj.updatePreview).toHaveBeenCalled(); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7902ccc21444f4547924fc00f970bac4b22c87aa --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js @@ -0,0 +1,172 @@ +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'Magento_Ui/js/grid/sticky/sticky' +], function (_, Sticky) { + 'use strict'; + + describe('ui/js/grid/sticky/sticky', function () { + var stickyObj, + data, + stub; + + Sticky.prototype.initialize = function () { + }; + + stickyObj = new Sticky({}); + + describe('has initialized', function () { + it('has been defined', function () { + expect(stickyObj).toBeDefined(); + }); + it('has initialized observable', function () { + data = stickyObj.initObservable(); + expect(data).toBeDefined(); + }); + it('has initListingNode method', function () { + spyOn(stickyObj, 'initListingNode'); + stickyObj.initListingNode(); + expect(stickyObj.initListingNode).toHaveBeenCalled(); + }); + it('has initStickyToolbarNode method', function () { + stickyObj.initStickyToolbarNode({}); + expect(stickyObj.stickyToolbarNode).toBeDefined(); + }); + it('has initContainerNode method', function () { + spyOn(stickyObj, 'initContainerNode'); + stickyObj.initContainerNode(); + expect(stickyObj.initContainerNode).toHaveBeenCalled(); + }); + it('has initListeners method', function () { + spyOn(stickyObj, 'initListeners'); + stickyObj.initListeners(); + expect(stickyObj.initListeners).toHaveBeenCalled(); + }); + it('has initOnScroll method', function () { + stickyObj.initOnScroll(); + expect(stickyObj.lastHorizontalScrollPos).toBeDefined(); + }); + it('has initOnListingScroll method', function () { + spyOn(stickyObj, 'initOnListingScroll'); + stickyObj.initOnListingScroll(); + expect(stickyObj.initOnListingScroll).toHaveBeenCalled(); + }); + it('has initOnResize method', function () { + spyOn(stickyObj, 'initOnResize'); + stickyObj.initOnResize(); + expect(stickyObj.initOnResize).toHaveBeenCalled(); + }); + }); + describe('has handlers', function () { + it('has onWindowScroll event', function () { + stickyObj.adjustOffset = function (){ + return this; + }; + + stickyObj.lastHorizontalScrollPos = 100500; + spyOn(stickyObj, 'adjustDataGridCapPositions'); + stickyObj.onWindowScroll(); + expect(stickyObj.adjustDataGridCapPositions).toHaveBeenCalled(); + }); + it('has onListingScroll method', function () { + spyOn(stickyObj, 'adjustOffset'); + stickyObj.onListingScroll(); + expect(stickyObj.adjustOffset).toHaveBeenCalled(); + }); + it('has onResize method', function () { + spyOn(stickyObj, 'onResize'); + stickyObj.onResize(); + expect(stickyObj.onResize).toHaveBeenCalled(); + }); + }); + describe('has getters', function () { + it('has getListingWidth', function () { + stickyObj.listingNode = { + width: function () { + return 100500; + } + }; + data = stickyObj.getListingWidth(); + expect(data).toBeDefined(); + }); + it('has getTableWidth method', function () { + spyOn(stickyObj, 'getTableWidth'); + stickyObj.getTableWidth(); + expect(stickyObj.getTableWidth).toHaveBeenCalled(); + }); + it('has getTopElement', function () { + stickyObj.toolbarNode = {}; + data = stickyObj.getTopElement(); + expect(data).toBeDefined(); + }); + it('has getOtherStickyElementsSize', function () { + stickyObj.otherStickyElsSize = null; + data = stickyObj.getOtherStickyElementsSize(); + expect(data).toEqual(stickyObj.otherStickyElsSize); + }); + it('has getListingTopYCoord method', function () { + spyOn(stickyObj, 'getListingTopYCoord'); + stickyObj.getListingTopYCoord(); + expect(stickyObj.getListingTopYCoord).toHaveBeenCalled(); + }); + it('has getMustBeSticky method', function () { + spyOn(stickyObj, 'getMustBeSticky'); + stickyObj.getMustBeSticky(); + expect(stickyObj.getMustBeSticky).toHaveBeenCalled(); + }); + }); + describe('has dom manipulators', function () { + it('has resizeContainer event', function () { + spyOn(stickyObj, 'resizeContainer'); + stickyObj.resizeContainer(); + expect(stickyObj.resizeContainer).toHaveBeenCalled(); + }); + it('has resizeCols event', function () { + spyOn(stickyObj, 'resizeCols'); + stickyObj.resizeCols(); + expect(stickyObj.resizeCols).toHaveBeenCalled(); + }); + it('has resetToTop event', function () { + spyOn(stickyObj, 'resetToTop'); + stickyObj.resetToTop(); + expect(stickyObj.resetToTop).toHaveBeenCalled(); + }); + it('has toggleContainerVisibility event', function () { + spyOn(stickyObj, 'visible'); + stickyObj.toggleContainerVisibility(); + expect(stickyObj.visible).toHaveBeenCalled(); + }); + it('has adjustContainerElemsWidth event', function () { + stickyObj.resizeContainer = function(){ + return this; + }; + stickyObj.resizeCols = function(){ + return this; + }; + spyOn(stickyObj, 'resizeBulk'); + stickyObj.adjustContainerElemsWidth(); + expect(stickyObj.resizeBulk).toHaveBeenCalled(); + }); + it('has adjustOffset event', function () { + spyOn(stickyObj, 'adjustOffset'); + stickyObj.adjustOffset(); + expect(stickyObj.adjustOffset).toHaveBeenCalled(); + }); + it('has checkPos event', function () { + stickyObj.visible = function(){ + return false; + }; + stickyObj.getMustBeSticky = function(){ + return false; + }; + + data = stickyObj.checkPos(); + expect(data).toBeDefined(); + }) + }); + }) +}); \ No newline at end of file diff --git a/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js b/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js index 3f076659b49239d26ac77fc5e593d511b4caf8f4..f198fee51b045e6a270758eda15d49e788531b3d 100644 --- a/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js +++ b/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js @@ -113,7 +113,7 @@ define([ }); it('breakpoints override configs', function () { - expect($('.fotorama__arr').css('display')).toBe('none'); + expect($('.fotorama__arr').css('display')).toBe('block'); }); }); }); diff --git a/dev/tests/performance/benchmark.jmx b/dev/tests/performance/benchmark.jmx index 5ad1b4dcc5b7b544402485f77fcb30d8c866a60f..7ad22b22a85cfd4f4402a74486e3af3a722bb009 100644 --- a/dev/tests/performance/benchmark.jmx +++ b/dev/tests/performance/benchmark.jmx @@ -930,11 +930,11 @@ </elementProp> <elementProp name="Content-Type" elementType="Header"> <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json; charset=UTF-8
</stringProp> + <stringProp name="Header.value">application/json; charset=UTF-8</stringProp> </elementProp> <elementProp name="X-Requested-With" elementType="Header"> <stringProp name="Header.name">X-Requested-With</stringProp> - <stringProp name="Header.value">XMLHttpRequest
</stringProp> + <stringProp name="Header.value">XMLHttpRequest</stringProp> </elementProp> <elementProp name="Accept" elementType="Header"> <stringProp name="Header.name">Accept</stringProp> @@ -996,7 +996,7 @@ </elementProp> <elementProp name="" elementType="Header"> <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json; charset=UTF-8 </stringProp> + <stringProp name="Header.value">application/json; charset=UTF-8</stringProp> </elementProp> <elementProp name="" elementType="Header"> <stringProp name="Header.name">Accept</stringProp> diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index 163b4ab3cd019ec4410edaebf94492a10e38e05b..99cb701eec3f576499260a2fce7ab75acdcc3797 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -7,8 +7,25 @@ */ namespace Magento\TestFramework\Dependency; -class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface +use Magento\Framework\App\Utility\Files; +use Magento\TestFramework\Dependency\RuleInterface; + +class PhpRule implements RuleInterface { + /** + * List of filepaths for DI files + * + * @var array + */ + private $diFiles; + + /** + * Map from plugin classes to the subjects they modify + * + * @var array + */ + private $pluginMap; + /** * List of routers * @@ -47,12 +64,14 @@ class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface * * @param array $mapRouters * @param array $mapLayoutBlocks + * @param array $pluginMap */ - public function __construct(array $mapRouters, array $mapLayoutBlocks) + public function __construct(array $mapRouters, array $mapLayoutBlocks, array $pluginMap = []) { $this->_mapRouters = $mapRouters; $this->_mapLayoutBlocks = $mapLayoutBlocks; $this->_namespaces = implode('|', \Magento\Framework\App\Utility\Files::init()->getNamespaces()); + $this->pluginMap = $pluginMap ?: null; } /** @@ -72,7 +91,7 @@ class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface $pattern = '~\b(?<class>(?<module>(' . implode( '_|', - \Magento\Framework\App\Utility\Files::init()->getNamespaces() + Files::init()->getNamespaces() ) . '[_\\\\])[a-zA-Z0-9]+)[a-zA-Z0-9_\\\\]*)\b~'; $dependenciesInfo = []; @@ -83,10 +102,16 @@ class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface if ($currentModule == $referenceModule) { continue; } + $dependencyClass = trim($matches['class'][$i]); + $currentClass = $this->getClassFromFilepath($file, $currentModule); + $dependencyType = $this->isPluginDependency($currentClass, $dependencyClass) + ? RuleInterface::TYPE_SOFT + : RuleInterface::TYPE_HARD; + $dependenciesInfo[] = [ 'module' => $referenceModule, - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, - 'source' => trim($matches['class'][$i]), + 'type' => $dependencyType, + 'source' => $dependencyClass, ]; } } @@ -102,6 +127,84 @@ class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface return $dependenciesInfo; } + /** + * Get class name from filename based on class/file naming conventions + * + * @param string $filepath + * @param string $module + * @return string + */ + private function getClassFromFilepath($filepath, $module) + { + $class = strstr($filepath, str_replace(['_', '\\', '/'], DIRECTORY_SEPARATOR, $module)); + $class = str_replace(DIRECTORY_SEPARATOR, '\\', strstr($class, '.php', true)); + return $class; + } + + /** + * @return array + * @throws \Exception + */ + private function loadDiFiles() + { + if (!$this->diFiles) { + $this->diFiles = Files::init()->getDiConfigs(); + } + return $this->diFiles; + } + + /** + * Generate an array of plugin info + * + * @return array + */ + private function loadPluginMap() + { + if (!$this->pluginMap) { + foreach ($this->loadDiFiles() as $filepath) { + $dom = new \DOMDocument(); + $dom->loadXML(file_get_contents($filepath)); + $typeNodes = $dom->getElementsByTagName('type'); + /** @var \DOMElement $type */ + foreach ($typeNodes as $type) { + /** @var \DOMElement $plugin */ + foreach ($type->getElementsByTagName('plugin') as $plugin) { + $subject = $type->getAttribute('name'); + $pluginType = $plugin->getAttribute('type'); + $this->pluginMap[$pluginType] = $subject; + } + } + } + } + return $this->pluginMap; + } + + /** + * Determine whether a the dependency relation is because of a plugin + * + * True IFF the dependent is a plugin for some class in the same module as the dependency. + * + * @param string $dependent + * @param string $dependency + * @return bool + */ + private function isPluginDependency($dependent, $dependency) + { + $pluginMap = $this->loadPluginMap(); + $subject = isset($pluginMap[$dependent]) + ? $pluginMap[$dependent] + : null; + if ($subject === $dependency) { + return true; + } else if ($subject) { + $subjectModule = substr($subject, 0, strpos($subject, '\\', 9)); // (strlen('Magento\\') + 1) === 9 + return strpos($dependency, $subjectModule) === 0; + } else { + return false; + } + } + + /** * Check get URL method * @@ -128,7 +231,7 @@ class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface foreach ($modules as $module) { $dependencies[] = [ 'module' => $module, - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, + 'type' => RuleInterface::TYPE_HARD, 'source' => $item['source'], ]; } @@ -166,7 +269,7 @@ class PhpRule implements \Magento\TestFramework\Dependency\RuleInterface $module = isset($check['module']) ? $check['module'] : null; if ($module) { $result[$module] = [ - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, + 'type' => RuleInterface::TYPE_HARD, 'source' => $match['source'], ]; } diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php index ed8660694377b51f79320972d79b3f50d4662adc..aa92d6ba72e0097ba2476ca0bbde8a9c2421d61a 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/PhpRuleTest.php @@ -16,7 +16,11 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase { $mapRoutes = ['someModule' => ['Magento\SomeModule'], 'anotherModule' => ['Magento\OneModule']]; $mapLayoutBlocks = ['area' => ['block.name' => ['Magento\SomeModule' => 'Magento\SomeModule']]]; - $this->model = new PhpRule($mapRoutes, $mapLayoutBlocks); + $pluginMap = [ + 'Magento\Module1\Plugin1' => 'Magento\Module1\Subject', + 'Magento\Module1\Plugin2' => 'Magento\Module2\Subject', + ]; + $this->model = new PhpRule($mapRoutes, $mapLayoutBlocks, $pluginMap); } public function testNonPhpGetDependencyInfo() @@ -26,22 +30,31 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase } /** - * @param string $module + * @param string $class * @param string $content * @param array $expected * @dataProvider getDependencyInfoDataProvider */ - public function testGetDependencyInfo($module, $content, array $expected) + public function testGetDependencyInfo($class, $content, array $expected) { - $this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', 'any', $content)); + $file = $this->makeMockFilepath($class); + $module = $this->getModuleFromClass($class); + $this->assertEquals($expected, $this->model->getDependencyInfo($module, 'php', $file, $content)); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function getDependencyInfoDataProvider() { return [ - ['Magento\SomeModule', 'something extends \Magento\SomeModule\Any\ClassName {', []], //1 - [ - 'Magento\AnotherModule', + 'Extend class in same module' => [ + 'Magento\SomeModule\SomeClass', + 'something extends \Magento\SomeModule\Any\ClassName {', + [] + ], + 'Extend class in different module' => [ + 'Magento\AnotherModule\SomeClass', 'something extends \Magento\SomeModule\Any\ClassName {', [ [ @@ -50,14 +63,14 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase 'source' => 'Magento\SomeModule\Any\ClassName', ] ] - ], // 2 - [ - 'Magento\SomeModule', + ], + 'getViewFileUrl in same module' => [ + 'Magento\SomeModule\SomeClass', '$this->getViewFileUrl("Magento_SomeModule::js/order-by-sku-failure.js")', [] - ], // 3 - [ - 'Magento\AnotherModule', + ], + 'getViewFileUrl in different module' => [ + 'Magento\AnotherModule\SomeClass', '$this->getViewFileUrl("Magento_SomeModule::js/order-by-sku-failure.js")', [ [ @@ -66,10 +79,14 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase 'source' => 'Magento_SomeModule', ] ] - ], //4 - ['Magento\SomeModule', '$this->helper("Magento\SomeModule\Any\ClassName")', []], //5 - [ - 'Magento\AnotherModule', + ], + 'Helper class from same module' => [ + 'Magento\SomeModule\SomeClass', + '$this->helper("Magento\SomeModule\Any\ClassName")', + [] + ], + 'Helper class from another module' => [ + 'Magento\AnotherModule\SomeClass', '$this->helper("Magento\SomeModule\Any\ClassName")', [ [ @@ -78,10 +95,14 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase 'source' => 'Magento\SomeModule\Any\ClassName', ] ] - ], //6 - ['Magento\SomeModule', '$this->getUrl("someModule")', []], // 7 - [ - 'Magento\AnotherModule', + ], + 'getUrl from same module' => [ + 'Magento\SomeModule\SomeClass', + '$this->getUrl("someModule")', + [] + ], + 'getUrl from another module' => [ + 'Magento\SomeModule\SomeClass', '$this->getUrl("anotherModule")', [ [ @@ -90,10 +111,13 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase 'source' => 'getUrl("anotherModule"', ] ] - ], //8 - ['Magento\SomeModule', '$this->getLayout()->getBlock(\'block.name\');', []], // 9 - [ - 'Magento\AnotherModule', + ], + 'getBlock from same module' => [ + 'Magento\SomeModule\SomeClass', + '$this->getLayout()->getBlock(\'block.name\');', [] + ], + 'getBlock from another module' => [ + 'Magento\AnotherModule\SomeClass', '$this->getLayout()->getBlock(\'block.name\');', [ [ @@ -102,7 +126,50 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase 'source' => 'getBlock(\'block.name\')', ] ] - ] // 10 + ], + 'Plugin on class in same module' => [ + 'Magento\Module1\Plugin1', + ', \Magento\Module1\Subject $variable', + [] + ], + 'Plugin depends on arbitrary class in same module' => [ + 'Magento\Module1\Plugin1', + ', \Magento\Module1\NotSubject $variable', + [] + ], + 'Plugin on class in different module' => [ + 'Magento\Module1\Plugin2', + 'Magento\Module2\Subject', + [ + [ + 'module' => 'Magento\Module2', + 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_SOFT, + 'source' => 'Magento\Module2\Subject', + ] + ], + ], + 'Plugin depends on arbitrary class in same module as subject' => [ + 'Magento\Module1\Plugin2', + 'Magento\Module2\NotSubject', + [ + [ + 'module' => 'Magento\Module2', + 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_SOFT, + 'source' => 'Magento\Module2\NotSubject', + ] + ] + ], + 'Plugin depends on arbitrary class in arbitrary module' => [ + 'Magento\Module1\Plugin2', + 'Magento\OtherModule\NotSubject', + [ + [ + 'module' => 'Magento\OtherModule', + 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, + 'source' => 'Magento\OtherModule\NotSubject', + ] + ] + ], ]; } @@ -141,4 +208,28 @@ class PhpRuleTest extends \PHPUnit_Framework_TestCase ] ]; } + + + /** + * Make some fake filepath to correspond to the class name + * + * @param string $class + * @return string + */ + private function makeMockFilepath($class) + { + return 'ClassRoot' . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; + } + + /** + * Get the module name like Magento\Module out of a classname, assuming for test purpose that + * all modules are from "Magento" vendor + * + * @param string $class + * @return string + */ + private function getModuleFromClass($class) + { + return substr($class, 0, strpos($class, '\\', 9)); // (strlen('Magento\\') + 1) === 9 + } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php index c756aa6b43d7606c6db7a99a393700b8592d5000..a127d864c33d646a95b04aca00f8a8574afaa21c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php @@ -20,7 +20,6 @@ return [ 'catalog_product_entity_int' => 'Magento\Catalog', 'catalog_product_entity_text' => 'Magento\Catalog', 'catalog_product_entity_varchar' => 'Magento\Catalog', - 'catalog_product_entity_group_price' => 'Magento\Catalog', 'catalog_product_bundle_option' => 'Magento\Bundle', 'catalog_product_index_price_bundle_opt_idx' => 'Magento\Bundle', 'catalog_product_index_price_bundle_opt_tmp' => 'Magento\Bundle', @@ -51,7 +50,6 @@ return [ 'catalog_product_flat' => 'Magento\Catalog', 'catalog_product_index_eav' => 'Magento\Catalog', 'catalog_product_index_eav_decimal' => 'Magento\Catalog', - 'catalog_product_index_group_price' => 'Magento\Catalog', 'catalog_product_index_price' => 'Magento\Catalog', 'catalog_product_index_tier_price' => 'Magento\Catalog', 'catalog_product_index_website' => 'Magento\Catalog', 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 a76f67f4761e344f9a3011e1ef6e8d4a6746048e..596f370dcd13defcdd7332b3b70466bc80670578 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 @@ -77,6 +77,7 @@ app/code/Magento/Checkout/view/frontend/web/js/action/set-shipping-information.j app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js app/code/Magento/Checkout/view/frontend/web/js/discount-codes.js app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js +app/code/Magento/Checkout/view/frontend/web/js/model/authentication-messages.js app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js @@ -101,7 +102,6 @@ app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor.js app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js app/code/Magento/Checkout/view/frontend/web/js/model/shipping-service.js app/code/Magento/Checkout/view/frontend/web/js/model/sidebar.js -app/code/Magento/Checkout/view/frontend/web/js/model/step-loader.js app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js app/code/Magento/Checkout/view/frontend/web/js/model/totals.js app/code/Magento/Checkout/view/frontend/web/js/model/url-builder.js @@ -118,6 +118,7 @@ app/code/Magento/Checkout/view/frontend/web/js/proceed-to-checkout.js app/code/Magento/Checkout/view/frontend/web/js/region-updater.js app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +app/code/Magento/Checkout/view/frontend/web/js/view/authentication-messages.js app/code/Magento/Checkout/view/frontend/web/js/view/authentication.js app/code/Magento/Checkout/view/frontend/web/js/view/beforePlaceOrder.js app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js @@ -161,7 +162,10 @@ app/code/Magento/Cms/view/adminhtml/requirejs-config.js app/code/Magento/Cms/view/adminhtml/web/js/folder-tree.js app/code/Magento/ConfigurableProduct/view/adminhtml/requirejs-config.js app/code/Magento/ConfigurableProduct/view/adminhtml/web/catalog/product/attribute.js +app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/advanced-pricing-handler.js app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/configurable.js +app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/options/price-type-handler.js +app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/product-grid.js app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/attributes_values.js app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/bulk.js app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/steps/select_attributes.js @@ -315,7 +319,9 @@ app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js app/code/Magento/Sales/view/frontend/web/orders-returns.js app/code/Magento/SalesRule/view/frontend/web/js/action/cancel-coupon.js app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js +app/code/Magento/SalesRule/view/frontend/web/js/model/payment/discount-messages.js app/code/Magento/SalesRule/view/frontend/web/js/view/cart/totals/discount.js +app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount-messages.js app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js app/code/Magento/Search/view/frontend/requirejs-config.js @@ -418,13 +424,13 @@ app/code/Magento/Ui/view/base/web/js/grid/export.js app/code/Magento/Ui/view/base/web/js/grid/filters/chips.js app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js app/code/Magento/Ui/view/base/web/js/grid/filters/group.js -app/code/Magento/Ui/view/base/web/js/grid/listing.js app/code/Magento/Ui/view/base/web/js/grid/massactions.js app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js app/code/Magento/Ui/view/base/web/js/grid/paging/sizes.js app/code/Magento/Ui/view/base/web/js/grid/provider.js app/code/Magento/Ui/view/base/web/js/grid/resize.js app/code/Magento/Ui/view/base/web/js/grid/search/search.js +app/code/Magento/Ui/view/base/web/js/grid/sticky/sticky.js app/code/Magento/Ui/view/base/web/js/grid/tree-massactions.js app/code/Magento/Ui/view/base/web/js/lib/class.js app/code/Magento/Ui/view/base/web/js/lib/collapsible.js @@ -438,6 +444,7 @@ app/code/Magento/Ui/view/base/web/js/lib/events.js app/code/Magento/Ui/view/base/web/js/lib/key-codes.js app/code/Magento/Ui/view/base/web/js/lib/ko/bind/after-render.js app/code/Magento/Ui/view/base/web/js/lib/ko/bind/class.js +app/code/Magento/Ui/view/base/web/js/lib/ko/bind/collapsible.js app/code/Magento/Ui/view/base/web/js/lib/ko/bind/datepicker.js app/code/Magento/Ui/view/base/web/js/lib/ko/bind/fadeVisible.js app/code/Magento/Ui/view/base/web/js/lib/ko/bind/i18n.js @@ -466,11 +473,13 @@ app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js app/code/Magento/Ui/view/base/web/js/lib/view/utils/async.js app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js +app/code/Magento/Ui/view/base/web/js/lib/view/utils/raf.js app/code/Magento/Ui/view/base/web/js/modal/alert.js app/code/Magento/Ui/view/base/web/js/modal/confirm.js app/code/Magento/Ui/view/base/web/js/modal/modal.js app/code/Magento/Ui/view/base/web/js/modal/modalToggle.js app/code/Magento/Ui/view/frontend/web/js/model/messageList.js +app/code/Magento/Ui/view/frontend/web/js/model/messages.js app/code/Magento/Ui/view/frontend/web/js/view/messages.js app/code/Magento/Ups/view/frontend/web/js/model/shipping-rates-validation-rules.js app/code/Magento/Ups/view/frontend/web/js/model/shipping-rates-validator.js @@ -532,10 +541,13 @@ dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/b dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/storage.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/view.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/columns.test.js +dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/group.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js +dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js +dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/tree-massactions.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/core.test.js dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/links.test.js diff --git a/dev/tests/static/testsuite/Magento/Test/Js/_files/jscs/.jscsrc b/dev/tests/static/testsuite/Magento/Test/Js/_files/jscs/.jscsrc index 49ab9e6c2cf2e3cb80b9a7cbb44a17855337ea8e..b8071503882e23246959a4b515edeb435a1f325c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Js/_files/jscs/.jscsrc +++ b/dev/tests/static/testsuite/Magento/Test/Js/_files/jscs/.jscsrc @@ -1,7 +1,4 @@ { - "plugins": [ - "jscs-jsdoc" - ], "disallowKeywords": [ "with", "continue" @@ -62,6 +59,7 @@ "beforeOpeningCurlyBrace": true, "beforeOpeningRoundBrace": true }, + "disallowNewlineBeforeBlockStatements": true, "disallowDanglingUnderscores": null, "disallowEmptyBlocks": true, "disallowMixedSpacesAndTabs": true, 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 137c50c4765e6b60cea535f4bcd5a006a8926dbe..e5bbe49c2390d61b487f401f8edafff4dc56205f 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 @@ -3847,4 +3847,23 @@ return [ ['Magento\Setup\Model\SampleData', 'Magento\SampleData\Model\SampleData'], ['Magento\Customer\Controller\Account\ResetPassword'], ['Magento\Customer\Controller\Account'], + ['Magento\ConfigurableProduct\Block\Product\Configurable\AssociatedSelector\Backend\Grid\ColumnSet'], + ['Magento\ConfigurableProduct\Model\Resource\Product\Collection\AssociatedProduct'], + ['Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\Renderer\Inventory'], + ['Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\Renderer\Checkbox'], + ['Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Grid\Filter\Inventory'], + ['Magento\Catalog\Block\Product\TemplateSelector'], + ['Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit\SuggestProductTemplates'], + ['Magento\Bundle\Controller\Adminhtml\Bundle\Product\Edit\SuggestProductTemplates'], + ['Magento\ConfigurableProduct\Block\Adminhtml\Product\Edit\Tab\Super\Config\Attribute'], + ['Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\GroupPrice'], + ['Magento\Catalog\Api\Data\ProductGroupPriceInterface'], + ['Magento\Catalog\Api\ProductGroupPriceManagementInterface'], + ['Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Price\Group'], + ['Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice'], + ['Magento\Catalog\Model\Product\GroupPrice'], + ['Magento\Catalog\Model\Product\GroupPriceManagement'], + ['Magento\Catalog\Model\Resource\Product\Attribute\Backend\GroupPrice'], + ['Magento\Catalog\Pricing\Price\GroupPrice'], + ['Magento\CatalogImportExport\Model\Import\Product\Validator\GroupPrice'], ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php index 353b7bef2f399d008fcee3a39a21802e0b9cb333..fa4ee8e1c8825fe8f62bf1d774f654f9aa7139b5 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php @@ -168,7 +168,6 @@ return [ [ 'XML_PATH_DEBUG_TEMPLATE_HINTS', 'Magento\Framework\View\Element\Template', - 'Magento\Core\Model\TemplateEngine\Plugin::XML_PATH_DEBUG_TEMPLATE_HINTS', ], [ 'XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS', diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php index 90f0fb5fa7649ab89164d4cb1ce451e004c69fa8..bdb2ccf51437ca9ad0fe315865302ab215101866 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php @@ -2460,4 +2460,11 @@ return [ 'Magento\ConfigurableProduct\Model\Product\VariationHandler::fillSimpleProductData' ], ['mergeBookmarkConfig', 'Magento\Ui\Model\Manager'], + ['getTitle', 'Magento\Quote\Api\Data\PaymentInterface'], + ['setTitle', 'Magento\Quote\Api\Data\PaymentInterface'], + ['getTitle', 'Magento\Quote\Model\Quote\Payment'], + ['setTitle', 'Magento\Quote\Model\Quote\Payment'], + ['getTypeSwitcherControlLabel', 'Magento\Catalog\Helper\Product'], + ['addStockStatusToSelect', 'Magento\CatalogInventory\Helper\Stock'], + ['getUrlInstance', 'Magento\Catalog\Model\Product\Url'], ]; diff --git a/dev/tools/grunt/configs/eslint.js b/dev/tools/grunt/configs/eslint.js index 9ce30aa58156ea22b011b2503f987453a6bdec7a..5dcbccf9ad4cd50686d89e43facc5d240bcb7048 100644 --- a/dev/tools/grunt/configs/eslint.js +++ b/dev/tools/grunt/configs/eslint.js @@ -19,7 +19,7 @@ module.exports = { configFile: 'dev/tests/static/testsuite/Magento/Test/Js/_files/eslint/.eslintrc', reset: true, outputFile: 'dev/tests/static/eslint-error-report.xml', - format: 'checkstyle', + format: 'junit', quiet: true }, src: '' diff --git a/dev/tools/grunt/configs/jscs.js b/dev/tools/grunt/configs/jscs.js index 8b89e0293bdb5de151a19266e8ffe5fe28d83de4..338deb017017d7dd50521f7090bb6268975fc6f8 100644 --- a/dev/tools/grunt/configs/jscs.js +++ b/dev/tools/grunt/configs/jscs.js @@ -16,7 +16,7 @@ module.exports = { options: { config: 'dev/tests/static/testsuite/Magento/Test/Js/_files/jscs/.jscsrc', reporterOutput: 'dev/tests/static/jscs-error-report.xml', - reporter: 'checkstyle' + reporter: 'junit' }, src: '' } diff --git a/lib/internal/Magento/Framework/Api/Data/VideoContentInterface.php b/lib/internal/Magento/Framework/Api/Data/VideoContentInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..270f2a20288d55eccc6814fcf4374a9c169df66b --- /dev/null +++ b/lib/internal/Magento/Framework/Api/Data/VideoContentInterface.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Api\Data; + +use Magento\Framework\Api\ExtensibleDataInterface; + +/** + * Video Content data interface + * + * @api + */ +interface VideoContentInterface extends ExtensibleDataInterface +{ + const TYPE = 'media_type'; + const PROVIDER = 'video_provider'; + const URL = 'video_url'; + const TITLE = 'video_title'; + const DESCRIPTION = 'video_description'; + const METADATA = 'video_metadata'; + + /** + * Retrieve MIME type + * + * @return string + */ + public function getMediaType(); + + /** + * Set MIME type + * + * @param string $mimeType + * @return $this + */ + public function setMediaType($mimeType); + + /** + * Get provider + * + * @return string + */ + public function getVideoProvider(); + + /** + * Set provider + * + * @param string $data + * @return $this + */ + public function setVideoProvider($data); + + /** + * Get video URL + * + * @return string + */ + public function getVideoUrl(); + + /** + * Set video URL + * + * @param string $data + * @return $this + */ + public function setVideoUrl($data); + + /** + * Get Title + * + * @return string + */ + public function getVideoTitle(); + + /** + * Set Title + * + * @param string $data + * @return $this + */ + public function setVideoTitle($data); + + /** + * Get video Description + * + * @return string + */ + public function getVideoDescription(); + + /** + * Set video Description + * + * @param string $data + * @return $this + */ + public function setVideoDescription($data); + + /** + * Get Metadata + * + * @return string + */ + public function getVideoMetadata(); + + /** + * Set Metadata + * + * @param string $data + * @return $this + */ + public function setVideoMetadata($data); +} diff --git a/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php index dbe83d1d343a238f1d5806b86120b89e1059e7b4..3f7ebf4ab4ae45d4b022f68ca4bf7a74e225328c 100644 --- a/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php +++ b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessor.php @@ -7,18 +7,14 @@ namespace Magento\Framework\Api\ExtensionAttribute; use Magento\Framework\Api\ExtensionAttribute\Config; -use Magento\Framework\Api\ExtensionAttribute\Config\Converter; +use Magento\Framework\Api\ExtensionAttribute\Config\Converter as Converter; use Magento\Framework\Data\Collection\AbstractDb as DbCollection; -use Magento\Framework\Api\ExtensionAttribute\JoinDataInterface; -use Magento\Framework\Api\ExtensionAttribute\JoinDataInterfaceFactory; use Magento\Framework\Reflection\TypeProcessor; use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Framework\Api\ExtensionAttributesFactory; -use Magento\Framework\Api\SimpleDataObjectConverter; /** * Join processor allows to join extension attributes during collections loading. - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface { @@ -29,47 +25,33 @@ class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinPro */ protected $objectManager; - /** - * @var Config - */ - private $config; - - /** - * @var JoinDataInterfaceFactory - */ - private $extensionAttributeJoinDataFactory; - - /** - * @var TypeProcessor - */ + /** @var TypeProcessor */ private $typeProcessor; - /** - * @var ExtensionAttributesFactory - */ + /** @var ExtensionAttributesFactory */ private $extensionAttributesFactory; + /** @var JoinProcessorHelper */ + private $joinProcessorHelper; + /** * Initialize dependencies. * * @param \Magento\Framework\ObjectManagerInterface $objectManager - * @param Config $config - * @param JoinDataInterfaceFactory $extensionAttributeJoinDataFactory * @param TypeProcessor $typeProcessor * @param ExtensionAttributesFactory $extensionAttributesFactory + * @param JoinProcessorHelper $joinProcessorHelper */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, - Config $config, - JoinDataInterfaceFactory $extensionAttributeJoinDataFactory, TypeProcessor $typeProcessor, - ExtensionAttributesFactory $extensionAttributesFactory + ExtensionAttributesFactory $extensionAttributesFactory, + JoinProcessorHelper $joinProcessorHelper ) { $this->objectManager = $objectManager; - $this->config = $config; - $this->extensionAttributeJoinDataFactory = $extensionAttributeJoinDataFactory; $this->typeProcessor = $typeProcessor; $this->extensionAttributesFactory = $extensionAttributesFactory; + $this->joinProcessorHelper = $joinProcessorHelper; } /** @@ -79,50 +61,22 @@ class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinPro { $extensibleEntityClass = $extensibleEntityClass ?: $collection->getItemObjectClass(); $joinDirectives = $this->getJoinDirectivesForType($extensibleEntityClass); + foreach ($joinDirectives as $attributeCode => $directive) { /** @var JoinDataInterface $joinData */ - $joinData = $this->extensionAttributeJoinDataFactory->create(); + $joinData = $this->joinProcessorHelper->getJoinDataInterface(); $joinData->setAttributeCode($attributeCode) ->setReferenceTable($directive[Converter::JOIN_REFERENCE_TABLE]) ->setReferenceTableAlias($this->getReferenceTableAlias($attributeCode)) ->setReferenceField($directive[Converter::JOIN_REFERENCE_FIELD]) ->setJoinField($directive[Converter::JOIN_ON_FIELD]); $joinData->setSelectFields( - $this->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS]) + $this->joinProcessorHelper->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS]) ); $collection->joinExtensionAttribute($joinData, $this); } } - /** - * Generate a list of select fields with mapping of client facing attribute names to field names used in SQL select. - * - * @param string $attributeCode - * @param array $selectFields - * @return array - */ - private function getSelectFieldsMap($attributeCode, $selectFields) - { - $referenceTableAlias = $this->getReferenceTableAlias($attributeCode); - $useFieldInAlias = (count($selectFields) > 1); - $selectFieldsAliases = []; - foreach ($selectFields as $selectField) { - $internalFieldName = $selectField[Converter::JOIN_FIELD_COLUMN] - ? $selectField[Converter::JOIN_FIELD_COLUMN] - : $selectField[Converter::JOIN_FIELD]; - $setterName = 'set' - . ucfirst(SimpleDataObjectConverter::snakeCaseToCamelCase($selectField[Converter::JOIN_FIELD])); - $selectFieldsAliases[] = [ - JoinDataInterface::SELECT_FIELD_EXTERNAL_ALIAS => $attributeCode - . ($useFieldInAlias ? '.' . $selectField[Converter::JOIN_FIELD] : ''), - JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS => $referenceTableAlias . '_' . $internalFieldName, - JoinDataInterface::SELECT_FIELD_WITH_DB_PREFIX => $referenceTableAlias . '.' . $internalFieldName, - JoinDataInterface::SELECT_FIELD_SETTER => $setterName - ]; - } - return $selectFieldsAliases; - } - /** * Generate reference table alias. * @@ -180,7 +134,9 @@ class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinPro $extensibleEntityClass ) { $attributeType = $directive[Converter::DATA_TYPE]; - $selectFields = $this->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS]); + $selectFields = $this->joinProcessorHelper + ->getSelectFieldsMap($attributeCode, $directive[Converter::JOIN_FIELDS]); + foreach ($selectFields as $selectField) { $internalAlias = $selectField[JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS]; if (isset($data[$internalAlias])) { @@ -223,7 +179,7 @@ class JoinProcessor implements \Magento\Framework\Api\ExtensionAttribute\JoinPro $extensibleInterfaceName = $this->extensionAttributesFactory ->getExtensibleInterfaceName($extensibleEntityClass); $extensibleInterfaceName = ltrim($extensibleInterfaceName, '\\'); - $config = $this->config->get(); + $config = $this->joinProcessorHelper->getConfigData(); if (!isset($config[$extensibleInterfaceName])) { return []; } diff --git a/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessorHelper.php b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessorHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..0ac6f092e18edd5aaa5c6396ed2ebf89fd2246e6 --- /dev/null +++ b/lib/internal/Magento/Framework/Api/ExtensionAttribute/JoinProcessorHelper.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Api\ExtensionAttribute; + +use Magento\Framework\Api\ExtensionAttribute\Config; +use Magento\Framework\Api\ExtensionAttribute\Config\Converter as Converter; +use Magento\Framework\Api\SimpleDataObjectConverter; + +/** + * Join processor helper class + */ +class JoinProcessorHelper +{ + /** @var Config */ + private $config; + + /** @var JoinDataInterfaceFactory */ + private $joinDataInterfaceFactory; + + /** + * Initialize dependencies. + * + * @param Config $config + * @param JoinDataInterfaceFactory $joinDataInterfaceFactory + */ + public function __construct( + Config $config, + JoinDataInterfaceFactory $joinDataInterfaceFactory + ) { + $this->config = $config; + $this->joinDataInterfaceFactory = $joinDataInterfaceFactory; + } + + /** + * Generate a list of select fields with mapping of client facing attribute names to field names used in SQL select. + * + * @param string $attributeCode + * @param array $selectFields + * @return array + */ + public function getSelectFieldsMap($attributeCode, $selectFields) + { + $referenceTableAlias = $this->getReferenceTableAlias($attributeCode); + $useFieldInAlias = (count($selectFields) > 1); + $selectFieldsAliases = []; + + foreach ($selectFields as $selectField) { + $internalFieldName = $selectField[Converter::JOIN_FIELD_COLUMN] + ? $selectField[Converter::JOIN_FIELD_COLUMN] + : $selectField[Converter::JOIN_FIELD]; + $setterName = 'set' + . ucfirst(SimpleDataObjectConverter::snakeCaseToCamelCase($selectField[Converter::JOIN_FIELD])); + $selectFieldsAliases[] = [ + JoinDataInterface::SELECT_FIELD_EXTERNAL_ALIAS => $attributeCode + . ($useFieldInAlias ? '.' . $selectField[Converter::JOIN_FIELD] : ''), + JoinDataInterface::SELECT_FIELD_INTERNAL_ALIAS => $referenceTableAlias . '_' . $internalFieldName, + JoinDataInterface::SELECT_FIELD_WITH_DB_PREFIX => $referenceTableAlias . '.' . $internalFieldName, + JoinDataInterface::SELECT_FIELD_SETTER => $setterName + ]; + } + return $selectFieldsAliases; + } + + /** + * Generate reference table alias. + * + * @param string $attributeCode + * @return string + */ + public function getReferenceTableAlias($attributeCode) + { + return 'extension_attribute_' . $attributeCode; + } + + /** + * Returns config data values + * + * @return array|mixed|null + */ + public function getConfigData() + { + return $this->config->get(); + } + + /** + * JoinDataInterface getter + * + * @return JoinDataInterface + */ + public function getJoinDataInterface() + { + return $this->joinDataInterfaceFactory->create(); + } +} diff --git a/lib/internal/Magento/Framework/Api/SearchCriteriaBuilder.php b/lib/internal/Magento/Framework/Api/SearchCriteriaBuilder.php index 82c08f3e5cf8fb0be701535d5207018d7c2c2045..8d078203c0d2c87fb687aafb5c6e3fd065a7a55e 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteriaBuilder.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteriaBuilder.php @@ -18,18 +18,26 @@ class SearchCriteriaBuilder extends AbstractSimpleObjectBuilder */ protected $_filterGroupBuilder; + /** + * @var \Magento\Framework\Api\FilterBuilder + */ + protected $filterBuilder; + /** * @param ObjectFactory $objectFactory * @param FilterGroupBuilder $filterGroupBuilder + * @param FilterBuilder $filterBuilder */ public function __construct( ObjectFactory $objectFactory, - FilterGroupBuilder $filterGroupBuilder + FilterGroupBuilder $filterGroupBuilder, + FilterBuilder $filterBuilder ) { parent::__construct( $objectFactory ); $this->_filterGroupBuilder = $filterGroupBuilder; + $this->filterBuilder = $filterBuilder; } /** @@ -58,6 +66,23 @@ class SearchCriteriaBuilder extends AbstractSimpleObjectBuilder return $this; } + /** + * @param string $field + * @param mixed $value + * @param string $conditionType + * @return $this + */ + public function addFilter($field, $value, $conditionType = 'eq') + { + $this->addFilters([ + $this->filterBuilder->setField($field) + ->setValue($value) + ->setConditionType($conditionType) + ->create() + ]); + return $this; + } + /** * Set filter groups * diff --git a/lib/internal/Magento/Framework/App/Cache/Type/Reflection.php b/lib/internal/Magento/Framework/App/Cache/Type/Reflection.php new file mode 100644 index 0000000000000000000000000000000000000000..5ad5d1e30a8cc7e9a2b4543199db7570ec50e30c --- /dev/null +++ b/lib/internal/Magento/Framework/App/Cache/Type/Reflection.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App\Cache\Type; + +/** + * System / Cache Management / Cache type "Reflection Data" + */ +class Reflection extends \Magento\Framework\Cache\Frontend\Decorator\TagScope +{ + /** + * Cache type code unique among all cache types + */ + const TYPE_IDENTIFIER = 'reflection'; + + /** + * Cache tag used to distinguish the cache type from all other cache + */ + const CACHE_TAG = 'REFLECTION'; + + /** + * @param \Magento\Framework\App\Cache\Type\FrontendPool $cacheFrontendPool + */ + public function __construct(\Magento\Framework\App\Cache\Type\FrontendPool $cacheFrontendPool) + { + parent::__construct($cacheFrontendPool->get(self::TYPE_IDENTIFIER), self::CACHE_TAG); + } +} diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 16bb120e24707e422e55880cb8482427cecaa40e..6608cb60426d4f4a735f9ccf27f3d7f5be65c1a7 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -23,6 +23,13 @@ class ConfigOptionsListConstants const CONFIG_PATH_DB_PREFIX = 'db/table_prefix'; const CONFIG_PATH_X_FRAME_OPT = 'x-frame-options'; const CONFIG_PATH_CACHE_HOSTS = 'http_cache_hosts'; + const CONFIG_PATH_BACKEND = 'backend'; + const CONFIG_PATH_INSTALL = 'install'; + const CONFIG_PATH_CRYPT = 'crypt'; + const CONFIG_PATH_SESSION = 'session'; + const CONFIG_PATH_DB = 'db'; + const CONFIG_PATH_RESOURCE = 'resource'; + const CONFIG_PATH_CACHE_TYPES = 'cache_types'; /**#@-*/ /**#@+ diff --git a/lib/internal/Magento/Framework/DB/Sql/ConcatExpression.php b/lib/internal/Magento/Framework/DB/Sql/ConcatExpression.php index b9d9ffb4a48ae07c1a1ae5645c2bd753d8109273..09c1d2a36aa4582b714b32d078ef5732cda8a6aa 100644 --- a/lib/internal/Magento/Framework/DB/Sql/ConcatExpression.php +++ b/lib/internal/Magento/Framework/DB/Sql/ConcatExpression.php @@ -45,7 +45,7 @@ class ConcatExpression extends \Zend_Db_Expr /** * Returns SQL expression - * TRIM(CONCAT_WS(separator, IFNULL(str1,''), IFNULL(str2,''), ...)) + * TRIM(CONCAT_WS(separator, IF(str1 <> '', str1, NULL), IF(str2 <> '', str2, NULL) ...)) * * @return string */ @@ -61,7 +61,7 @@ class ConcatExpression extends \Zend_Db_Expr . (isset($part['columnName']) ? $part['columnName'] : $key) ); } - $columns[] = $this->adapter->getIfNullSql($column, "''"); + $columns[] = $this->adapter->getCheckSql($column . " <> ''", $column, 'NULL'); } return sprintf( 'TRIM(%s)', diff --git a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php index af8eb41ae628399e7bb63b73ed56384c0d3213d7..2b5ca736910b3717464f818fd1bb9484f2771681 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php @@ -241,7 +241,8 @@ abstract class AbstractElement extends AbstractForm 'placeholder', 'data-form-part', 'data-role', - 'data-action' + 'data-action', + 'checked', ]; } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Radios.php b/lib/internal/Magento/Framework/Data/Form/Element/Radios.php index 2fde6e9fd916eb1573f094b88e04f0cc0fc42e2e..a1e0849ee0bd02e3ce83ff1f7ed9ed8c7e441261 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Radios.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Radios.php @@ -31,18 +31,6 @@ class Radios extends AbstractElement $this->setType('radios'); } - /** - * @return mixed - */ - public function getSeparator() - { - $separator = $this->getData('separator'); - if ($separator === null) { - $separator = ' '; - } - return $separator; - } - /** * @return string */ @@ -66,21 +54,22 @@ class Radios extends AbstractElement */ protected function _optionToHtml($option, $selected) { - $html = '<input type="radio"' . $this->serialize(['name', 'class', 'style']); + $html = '<div class="admin__field admin__field-option">' . + '<input type="radio"' . $this->getRadioButtonAttributes($option); if (is_array($option)) { $html .= 'value="' . $this->_escape( $option['value'] - ) . '" id="' . $this->getHtmlId() . $option['value'] . '"'; + ) . '" class="admin__control-radio" id="' . $this->getHtmlId() . $option['value'] . '"'; if ($option['value'] == $selected) { $html .= ' checked="checked"'; } $html .= ' />'; - $html .= '<label class="inline" for="' . + $html .= '<label class="admin__field-label" for="' . $this->getHtmlId() . $option['value'] . - '">' . + '"><span>' . $option['label'] . - '</label>'; + '</span></label>'; } elseif ($option instanceof \Magento\Framework\DataObject) { $html .= 'id="' . $this->getHtmlId() . $option->getValue() . '"' . $option->serialize( ['label', 'title', 'value', 'class', 'style'] @@ -96,7 +85,31 @@ class Radios extends AbstractElement $option->getLabel() . '</label>'; } - $html .= $this->getSeparator() . "\n"; + $html .= '</div>'; + return $html; + } + + /** + * @return array + */ + public function getHtmlAttributes() + { + return array_merge(parent::getHtmlAttributes(), ['name']); + } + + /** + * @param array $option + * @return string + */ + protected function getRadioButtonAttributes($option) + { + $html = ''; + foreach ($this->getHtmlAttributes() as $attribute) { + if ($value = $this->getDataUsingMethod($attribute, $option['value'])) { + $html .= ' ' . $attribute . '="' . $value . '" '; + } + } return $html; + } } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php b/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php index 9fb3041882565a706fa79b15eee2b1893222f815..0876aeed4ab63ffade0d6b30d3b24dc27de8fe63 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Textarea.php @@ -80,7 +80,7 @@ class Textarea extends AbstractElement */ public function getElementHtml() { - $this->addClass('textarea'); + $this->addClass('textarea admin__control-textarea'); $html = '<textarea id="' . $this->getHtmlId() . '" name="' . $this->getName() . '" ' . $this->serialize($this->getHtmlAttributes()) . $this->_getUiId() . ' >'; $html .= $this->getEscapedValue(); diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php index 0bd453136959f8d2e2f5b1a422fa817443baa785..b601ed21a9e2024f9f50ddedfc7d36d4cf43a30d 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php @@ -206,7 +206,8 @@ class AbstractElementTest extends \PHPUnit_Framework_TestCase 'placeholder', 'data-form-part', 'data-role', - 'data-action' + 'data-action', + 'checked', ]; $this->assertEquals($htmlAttributes, $this->_model->getHtmlAttributes()); } diff --git a/lib/internal/Magento/Framework/Encryption/Encryptor.php b/lib/internal/Magento/Framework/Encryption/Encryptor.php index cc8c28feae760b3762c5a4a8b3fa8997fd793b41..26d237d823d0208683c01e16c90115c0d57b0fab 100644 --- a/lib/internal/Magento/Framework/Encryption/Encryptor.php +++ b/lib/internal/Magento/Framework/Encryption/Encryptor.php @@ -7,26 +7,49 @@ namespace Magento\Framework\Encryption; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Encryption\Helper\Security; +use Magento\Framework\Math\Random; /** - * Provides basic logic for hashing passwords and encrypting/decrypting misc data + * Class Encryptor provides basic logic for hashing strings and encrypting/decrypting misc data */ class Encryptor implements EncryptorInterface { /** - * Array key of encryption key in deployment config - */ - const PARAM_CRYPT_KEY = 'crypt/key'; - - /**#@+ - * Hash and Cipher versions + * Key of md5 algorithm */ const HASH_VERSION_MD5 = 0; + /** + * Key of sha256 algorithm + */ const HASH_VERSION_SHA256 = 1; + /** + * Key of latest used algorithm + */ const HASH_VERSION_LATEST = 1; + /** + * Default length of salt in bytes + */ + const DEFAULT_SALT_LENGTH = 32; + + /**#@+ + * Exploded password hash keys + */ + const PASSWORD_HASH = 0; + const PASSWORD_SALT = 1; + const PASSWORD_VERSION = 2; + /**#@-*/ + + /** + * Array key of encryption key in deployment config + */ + const PARAM_CRYPT_KEY = 'crypt/key'; + + /**#@+ + * Cipher versions + */ const CIPHER_BLOWFISH = 0; const CIPHER_RIJNDAEL_128 = 1; @@ -37,9 +60,26 @@ class Encryptor implements EncryptorInterface /**#@-*/ /** - * Default length of salt in bytes + * Default hash string delimiter */ - const DEFAULT_SALT_LENGTH = 32; + const DELIMITER = ':'; + + /** + * @var array map of hash versions + */ + private $hashVersionMap = [ + self::HASH_VERSION_MD5 => 'md5', + self::HASH_VERSION_SHA256 => 'sha256' + ]; + + /** + * @var array map of password hash + */ + private $passwordHashMap = [ + self::PASSWORD_HASH => '', + self::PASSWORD_SALT => '', + self::PASSWORD_VERSION => self::HASH_VERSION_LATEST + ]; /** * Indicate cipher @@ -63,19 +103,15 @@ class Encryptor implements EncryptorInterface protected $keys = []; /** - * @var \Magento\Framework\Math\Random - */ - protected $randomGenerator; - - /** - * @param \Magento\Framework\Math\Random $randomGenerator + * @param Random $random * @param DeploymentConfig $deploymentConfig */ public function __construct( - \Magento\Framework\Math\Random $randomGenerator, + Random $random, DeploymentConfig $deploymentConfig ) { - $this->randomGenerator = $randomGenerator; + $this->random = $random; + // load all possible keys $this->keys = preg_split('/\s+/s', trim($deploymentConfig->get(self::PARAM_CRYPT_KEY))); $this->keyVersion = count($this->keys) - 1; @@ -102,19 +138,9 @@ class Encryptor implements EncryptorInterface } /** - * Generate a [salted] hash. - * - * $salt can be: - * false - salt is not used - * true - random salt of the default length will be generated - * integer - random salt of specified length will be generated - * string - actual salt value to be used - * - * @param string $password - * @param bool|int|string $salt - * @return string + * @inheritdoc */ - public function getHash($password, $salt = false) + public function getHash($password, $salt = false, $version = self::HASH_VERSION_LATEST) { if ($salt === false) { return $this->hash($password); @@ -123,33 +149,29 @@ class Encryptor implements EncryptorInterface $salt = self::DEFAULT_SALT_LENGTH; } if (is_integer($salt)) { - $salt = $this->randomGenerator->getRandomString($salt); + $salt = $this->random->getRandomString($salt); } - return $this->hash($salt . $password) . ':' . $salt; + + return implode( + self::DELIMITER, + [ + $this->hash($salt . $password), + $salt, + $version + ] + ); } /** - * Hash a string - * - * @param string $data - * @param int $version - * @return string + * @inheritdoc */ public function hash($data, $version = self::HASH_VERSION_LATEST) { - if (self::HASH_VERSION_MD5 === $version) { - return md5($data); - } - return hash('sha256', $data); + return hash($this->hashVersionMap[$version], $data); } /** - * Validate hash against hashing method (with or without salt) - * - * @param string $password - * @param string $hash - * @return bool - * @deprecated + * @inheritdoc */ public function validateHash($password, $hash) { @@ -157,43 +179,72 @@ class Encryptor implements EncryptorInterface } /** - * Validate hash against hashing method (with or without salt) - * - * @param string $password - * @param string $hash - * @return bool + * @inheritdoc */ public function isValidHash($password, $hash) { - return $this->isValidHashByVersion( - $password, - $hash, - self::HASH_VERSION_SHA256 - ) || $this->isValidHashByVersion( + $this->explodePasswordHash($hash); + + foreach ($this->getPasswordVersion() as $hashVersion) { + $password = $this->hash($this->getPasswordSalt() . $password, $hashVersion); + } + + return Security::compareStrings( $password, - $hash, - self::HASH_VERSION_MD5 + $this->getPasswordHash() ); } /** - * Validate hash by specified version - * - * @param string $password + * @inheritdoc + */ + public function validateHashVersion($hash, $validateCount = false) + { + $this->explodePasswordHash($hash); + $hashVersions = $this->getPasswordVersion(); + + return $validateCount + ? end($hashVersions) === self::HASH_VERSION_LATEST && count($hashVersions) === 1 + : end($hashVersions) === self::HASH_VERSION_LATEST; + } + + /** * @param string $hash - * @param int $version - * @return bool + * @return array */ - public function isValidHashByVersion($password, $hash, $version) + private function explodePasswordHash($hash) { - // look for salt - $hashArr = explode(':', $hash, 2); - if (1 === count($hashArr)) { - return Security::compareStrings($this->hash($password, $version), $hash); + $explodedPassword = explode(self::DELIMITER, $hash, 3); + + foreach ($this->passwordHashMap as $key => $defaultValue) { + $this->passwordHashMap[$key] = (isset($explodedPassword[$key])) ? $explodedPassword[$key] : $defaultValue; } - list($hash, $salt) = $hashArr; - return Security::compareStrings($this->hash($salt . $password, $version), $hash); + return $this->passwordHashMap; + } + + /** + * @return string + */ + private function getPasswordHash() + { + return (string)$this->passwordHashMap[self::PASSWORD_HASH]; + } + + /** + * @return string + */ + private function getPasswordSalt() + { + return (string)$this->passwordHashMap[self::PASSWORD_SALT]; + } + + /** + * @return array + */ + private function getPasswordVersion() + { + return array_map('intval', explode(self::DELIMITER, $this->passwordHashMap[self::PASSWORD_VERSION])); } /** diff --git a/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php b/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php index 1e72deaed4502f09dcf518c910cee5193691bd21..0af8a165a977a2bfcf4837b9c30de4ac28a02341 100644 --- a/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php +++ b/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php @@ -55,14 +55,13 @@ interface EncryptorInterface public function isValidHash($password, $hash); /** - * Validate hash by specified version + * Validate hashing algorithm version * - * @param string $password * @param string $hash - * @param int $version + * @param bool $validateCount * @return bool */ - public function isValidHashByVersion($password, $hash, $version); + public function validateHashVersion($hash, $validateCount = false); /** * Encrypt a string diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php index f509f3c6eabfb603ae14bbcaf36e92e755c100a1..8dfdafa68c9a835e3bbb43a62321dcc2c66a0d45 100644 --- a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php @@ -43,19 +43,20 @@ class EncryptorTest extends \PHPUnit_Framework_TestCase public function testGetHashSpecifiedSalt() { $this->_randomGenerator->expects($this->never())->method('getRandomString'); - $expected = '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt'; + $expected = '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt:1'; $actual = $this->_model->getHash('password', 'salt'); $this->assertEquals($expected, $actual); } public function testGetHashRandomSaltDefaultLength() { + $salt = '-----------random_salt----------'; $this->_randomGenerator ->expects($this->once()) ->method('getRandomString') ->with(32) - ->will($this->returnValue('-----------random_salt----------')); - $expected = 'a1c7fc88037b70c9be84d3ad12522c7888f647915db78f42eb572008422ba2fa:-----------random_salt----------'; + ->will($this->returnValue($salt)); + $expected = 'a1c7fc88037b70c9be84d3ad12522c7888f647915db78f42eb572008422ba2fa:' . $salt . ':1'; $actual = $this->_model->getHash('password', true); $this->assertEquals($expected, $actual); } @@ -67,7 +68,7 @@ class EncryptorTest extends \PHPUnit_Framework_TestCase ->method('getRandomString') ->with(11) ->will($this->returnValue('random_salt')); - $expected = '4c5cab8dd00137d11258f8f87b93fd17bd94c5026fc52d3c5af911dd177a2611:random_salt'; + $expected = '4c5cab8dd00137d11258f8f87b93fd17bd94c5026fc52d3c5af911dd177a2611:random_salt:1'; $actual = $this->_model->getHash('password', 11); $this->assertEquals($expected, $actual); } @@ -88,10 +89,9 @@ class EncryptorTest extends \PHPUnit_Framework_TestCase public function validateHashDataProvider() { return [ - ['password', 'hash', false], - ['password', 'hash:salt', false], - ['password', '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8', true], - ['password', '67a1e09bb1f83f5007dc119c14d663aa:salt', true], + ['password', 'hash:salt:1', false], + ['password', '67a1e09bb1f83f5007dc119c14d663aa:salt:0', true], + ['password', '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt:1', true], ]; } diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php b/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php index 809d86bb20bc0fb59cf7ae911acced7c045c9553..1fe412f5479cd33494539b8b8bf2b5cf43ce0907 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php @@ -27,7 +27,7 @@ class WriteFactory } /** - * Create a readable directory + * Create a writable directory * * @param string $path * @param string $driverCode diff --git a/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/CollectionTest.php b/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/CollectionTest.php index 5b9917a935c1cf1738caf40977ffbd341499fd04..3b6a28728b87667edb37649366efbf8adb368b71 100644 --- a/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/CollectionTest.php +++ b/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/CollectionTest.php @@ -53,7 +53,6 @@ class CollectionTest extends \PHPUnit_Framework_TestCase [ 'regular_price' => 'RegularPrice', 'special_price' => 'SpecialPrice', - 'group_price' => 'GroupPrice', ] ); diff --git a/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/PoolTest.php b/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/PoolTest.php index 3bee8c2edb8c2c9f6164f5688114c14d51139066..c7fe5ba818d224acc2bfd40778056dc92976c72c 100644 --- a/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/PoolTest.php +++ b/lib/internal/Magento/Framework/Pricing/Test/Unit/Price/PoolTest.php @@ -43,7 +43,6 @@ class PoolTest extends \PHPUnit_Framework_TestCase 'special_price' => 'SpecialPrice', ]; $this->target = [ - 'group_price' => 'TargetGroupPrice', 'regular_price' => 'TargetRegularPrice', ]; $this->targetPool = new Pool($this->target); @@ -58,7 +57,6 @@ class PoolTest extends \PHPUnit_Framework_TestCase $expected = new Pool([ 'regular_price' => 'RegularPrice', 'special_price' => 'SpecialPrice', - 'group_price' => 'TargetGroupPrice', ]); $this->assertEquals($expected, $this->pool); } @@ -70,7 +68,6 @@ class PoolTest extends \PHPUnit_Framework_TestCase { $this->assertEquals('RegularPrice', $this->pool->get('regular_price')); $this->assertEquals('SpecialPrice', $this->pool->get('special_price')); - $this->assertEquals('TargetGroupPrice', $this->pool->get('group_price')); } /** @@ -80,7 +77,6 @@ class PoolTest extends \PHPUnit_Framework_TestCase { $this->assertEquals('RegularPrice', $this->pool['regular_price']); $this->assertEquals('SpecialPrice', $this->pool['special_price']); - $this->assertEquals('TargetGroupPrice', $this->pool['group_price']); $this->pool['fake_price'] = 'FakePrice'; $this->assertEquals('FakePrice', $this->pool['fake_price']); $this->assertTrue(isset($this->pool['fake_price'])); diff --git a/lib/internal/Magento/Framework/View/Asset/Bundle/Manager.php b/lib/internal/Magento/Framework/View/Asset/Bundle/Manager.php index 0b1c7f2cd5b05d20d37b951012650d3044cf7c65..b461ff19c638047aa68717fbf24060e0e51c8d4b 100644 --- a/lib/internal/Magento/Framework/View/Asset/Bundle/Manager.php +++ b/lib/internal/Magento/Framework/View/Asset/Bundle/Manager.php @@ -10,6 +10,7 @@ use Magento\Framework\View\Asset; use Magento\Framework\Filesystem; use Magento\Framework\View\Asset\Bundle; use Magento\Framework\View\Asset\LocalInterface; +use Magento\Framework\App\Filesystem\DirectoryList; /** * BundleService model @@ -126,7 +127,7 @@ class Manager /** @var $asset LocalInterface */ $filePathInfo = $this->splitPath($filePath); if ($filePathInfo && $this->compareModules($filePathInfo, $asset)) { - return $asset->getFilePath() == $filePathInfo['excludedPath']; + return $asset->getSourceFile() == $filePathInfo['excludedPath']; } return false; } @@ -190,33 +191,21 @@ class Manager protected function isAssetMinification(LocalInterface $asset) { $sourceFile = $asset->getSourceFile(); - if (in_array($asset->getFilePath(), $this->excluded)) { + $extension = $asset->getContentType(); + if (in_array($sourceFile, $this->excluded)) { return false; } - if ($this->minification->isEnabled($asset->getContentType())) { - if (strpos($sourceFile, '.min.') !== false) { - $this->excluded[] = str_replace('.min.', '', $sourceFile); - return true; - } - - $extension = $asset->getContentType(); - $minAbsolutePath = str_replace($extension, "min.{$extension}", $sourceFile); - if (file_exists($minAbsolutePath)) { - return false; - } - - return true; - } - - if (strpos($sourceFile, '.min.') !== false) { - $absolutePath = str_replace('.min.', '', $asset->getFilePath()); - if (file_exists($absolutePath)) { + if (strpos($sourceFile, '.min.') === false) { + $info = pathinfo($asset->getPath()); + $assetMinifiedPath = $info['dirname'] . '/' . $info['filename'] . '.min.' . $info['extension']; + if ($this->filesystem->getDirectoryRead(DirectoryList::APP)->isExist($assetMinifiedPath)) { + $this->excluded[] = $sourceFile; return false; } } else { - $extension = $asset->getContentType(); - $this->excluded[] = str_replace($extension, "min.{$extension}", $asset->getFilePath()); + $this->excluded[] = $this->filesystem->getDirectoryRead(DirectoryList::APP) + ->getAbsolutePath(str_replace(".min.$extension", ".$extension", $asset->getPath())); } return true; diff --git a/lib/internal/Magento/Framework/View/Asset/File.php b/lib/internal/Magento/Framework/View/Asset/File.php index 8354e2aa3d417b93690f41168861b073a5da4633..19a8026f4668d0cbea5502c50fac40bfebd47e53 100644 --- a/lib/internal/Magento/Framework/View/Asset/File.php +++ b/lib/internal/Magento/Framework/View/Asset/File.php @@ -6,8 +6,6 @@ namespace Magento\Framework\View\Asset; -use Magento\Framework\View\Asset\ConfigInterface as AssetConfigInterface; - /** * A locally available static view file asset that can be referred with a file path * diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php index f3518c2fc3022994c9e14f9280908b5ad01d669b..d47a76a4fe58c928e247cf4d98790b8ef6258eea 100644 --- a/lib/internal/Magento/Framework/View/Asset/Source.php +++ b/lib/internal/Magento/Framework/View/Asset/Source.php @@ -122,7 +122,6 @@ class Source private function preProcess(LocalInterface $asset) { $sourceFile = $this->findSourceFile($asset); - $dirCode = DirectoryList::ROOT; $path = $this->rootDir->getRelativePath($sourceFile); $chain = $this->createChain($asset, $path); diff --git a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php index 7c971eaf437f13a4b301b4b210affbea20ce35d8..f1daf86f782395e831a804a55369554acd28e29f 100644 --- a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php +++ b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php @@ -12,6 +12,7 @@ namespace Magento\Framework\View\Element\Html\Link; * @method string getLabel() * @method string getPath() * @method string getTitle() + * @method null|array getAttributes() * @method null|bool getCurrent() * @method \Magento\Framework\View\Element\Html\Link\Current setCurrent(bool $value) */ @@ -110,7 +111,7 @@ class Current extends \Magento\Framework\View\Element\Template $html .= $this->getTitle() ? ' title="' . $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getTitle())) . '"' : ''; - $html .= '>'; + $html .= $this->getAttributesHtml() . '>'; if ($this->getIsHighlighted()) { $html .= '<strong>'; @@ -127,4 +128,22 @@ class Current extends \Magento\Framework\View\Element\Template return $html; } + + /** + * Generate attributes' HTML code + * + * @return string + */ + private function getAttributesHtml() + { + $attributesHtml = ''; + $attributes = $this->getAttributes(); + if ($attributes) { + foreach ($attributes as $attribute => $value) { + $attributesHtml .= ' ' . $attribute . '="' . $this->escapeHtml($value) . '"'; + } + } + + return $attributesHtml; + } } diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/ContentType/Json.php b/lib/internal/Magento/Framework/View/Element/UiComponent/ContentType/Json.php index 6af04342a3a6c7e37af552809ae0348d7820e6d0..abcd9be5823f5c8a8635271ebdb6b6d071c4b07b 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/ContentType/Json.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/ContentType/Json.php @@ -9,6 +9,7 @@ use Magento\Framework\Json\Encoder; use Magento\Framework\View\FileSystem; use Magento\Framework\View\TemplateEnginePool; use Magento\Framework\View\Element\UiComponentInterface; +use Magento\Framework\View\Layout\Generator\Structure; /** * Class Json @@ -16,6 +17,15 @@ use Magento\Framework\View\Element\UiComponentInterface; class Json extends AbstractContentType { /** + * Generator structure instance + * + * @var Structure + */ + private $structure; + + /** + * Encoder + * * @var Encoder */ private $encoder; @@ -26,14 +36,17 @@ class Json extends AbstractContentType * @param FileSystem $filesystem * @param TemplateEnginePool $templateEnginePool * @param Encoder $encoder + * @param Structure $structure */ public function __construct( FileSystem $filesystem, TemplateEnginePool $templateEnginePool, - Encoder $encoder + Encoder $encoder, + Structure $structure ) { parent::__construct($filesystem, $templateEnginePool); $this->encoder = $encoder; + $this->structure = $structure; } /** @@ -47,9 +60,15 @@ class Json extends AbstractContentType */ public function render(UiComponentInterface $component, $template = '') { - $data = $component->getContext()->getDataSourceData($component); - $data = reset($data); - - return $this->encoder->encode($data['config']['data']); + $context = $component->getContext(); + $isComponent = $context->getRequestParam('componentJson'); + if ($isComponent) { + $data = $this->structure->generate($component); + return $this->encoder->encode($data); + } else { + $data = $component->getContext()->getDataSourceData($component); + $data = reset($data); + return $this->encoder->encode($data['config']['data']); + } } } diff --git a/app/code/Magento/Ui/Component/Layout/Generator/Structure.php b/lib/internal/Magento/Framework/View/Layout/Generator/Structure.php similarity index 90% rename from app/code/Magento/Ui/Component/Layout/Generator/Structure.php rename to lib/internal/Magento/Framework/View/Layout/Generator/Structure.php index c26b9c40664a3bd93122a7f6bee509264fcb8049..49431962a86b2a46568771d437cedd46aef016ef 100644 --- a/app/code/Magento/Ui/Component/Layout/Generator/Structure.php +++ b/lib/internal/Magento/Framework/View/Layout/Generator/Structure.php @@ -3,9 +3,9 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Ui\Component\Layout\Generator; +namespace Magento\Framework\View\Layout\Generator; -use Magento\Ui\Component\Layout\LayoutPool; +use Magento\Framework\View\Layout\Pool as LayoutPool; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\View\Element\UiComponent\LayoutInterface; diff --git a/lib/internal/Magento/Framework/View/Layout/Generator/UiComponent.php b/lib/internal/Magento/Framework/View/Layout/Generator/UiComponent.php index d5a8fcda61c2a164f1da15f4f9f3482112d18fe1..a1d81de2f22f6b8a3f63b2deca2d333714c9a754 100644 --- a/lib/internal/Magento/Framework/View/Layout/Generator/UiComponent.php +++ b/lib/internal/Magento/Framework/View/Layout/Generator/UiComponent.php @@ -7,7 +7,7 @@ namespace Magento\Framework\View\Layout\Generator; use Magento\Framework\View\Layout; use Magento\Framework\View\Element\BlockFactory; -use Magento\Framework\View\Layout\Data\Structure; +use Magento\Framework\View\Layout\Data\Structure as DataStructure; use Magento\Framework\View\Layout\GeneratorInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\View\Element\UiComponentInterface; @@ -102,13 +102,13 @@ class UiComponent implements GeneratorInterface /** * Create component object * - * @param Structure $structure + * @param DataStructure $structure * @param string $elementName * @param string $data * @param LayoutInterface $layout * @return ContainerInterface */ - protected function generateComponent(Structure $structure, $elementName, $data, LayoutInterface $layout) + protected function generateComponent(DataStructure $structure, $elementName, $data, LayoutInterface $layout) { $attributes = $data['attributes']; if (!empty($attributes['group'])) { diff --git a/app/code/Magento/Ui/Component/Layout/Generic.php b/lib/internal/Magento/Framework/View/Layout/Generic.php similarity index 98% rename from app/code/Magento/Ui/Component/Layout/Generic.php rename to lib/internal/Magento/Framework/View/Layout/Generic.php index 92d0a406f38e067fd5fc189ae3e1821fd788eeac..a64af6ec44b46ccc2eb0324f51a2c600995fb1e3 100644 --- a/app/code/Magento/Ui/Component/Layout/Generic.php +++ b/lib/internal/Magento/Framework/View/Layout/Generic.php @@ -3,7 +3,7 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Ui\Component\Layout; +namespace Magento\Framework\View\Layout; use Magento\Framework\View\Element\UiComponent\DataSourceInterface; use Magento\Framework\View\Element\UiComponent\LayoutInterface; diff --git a/app/code/Magento/Ui/Component/Layout/LayoutPool.php b/lib/internal/Magento/Framework/View/Layout/Pool.php similarity index 90% rename from app/code/Magento/Ui/Component/Layout/LayoutPool.php rename to lib/internal/Magento/Framework/View/Layout/Pool.php index b15a513ff42c604797718731bb16798f670b95a4..d9d785a85884ed15879f37e3961f3211b156c2e3 100644 --- a/app/code/Magento/Ui/Component/Layout/LayoutPool.php +++ b/lib/internal/Magento/Framework/View/Layout/Pool.php @@ -3,17 +3,17 @@ * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Ui\Component\Layout; +namespace Magento\Framework\View\Layout; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\View\Element\UiComponent\LayoutInterface; /** - * Class LayoutPool + * Class Pool */ -class LayoutPool +class Pool { - const DEFAULT_CLASS = 'Magento\Ui\Component\Layout\Generic'; + const DEFAULT_CLASS = 'Magento\Framework\View\Layout\Generic'; /** * Layouts pool diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index f01721a2704d401083bc98b5b86245d9d52b2e24..1816c6a1b6d613243360a9fa7ba23829d62af2d1 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -37,6 +37,11 @@ class Config */ const BODY_ATTRIBUTE_CLASS = 'class'; + /** + * Constant html language attribute + */ + const HTML_ATTRIBUTE_LANG = 'lang'; + /** * Allowed group of types * @@ -85,6 +90,11 @@ class Config */ protected $favicon; + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + protected $localeResolver; + /** * @var \Magento\Framework\View\Layout\BuilderInterface */ @@ -113,19 +123,27 @@ class Config * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\View\Page\FaviconInterface $favicon * @param Title $title + * @param \Magento\Framework\Locale\ResolverInterface $localeResolver */ public function __construct( View\Asset\Repository $assetRepo, View\Asset\GroupedCollection $pageAssets, App\Config\ScopeConfigInterface $scopeConfig, View\Page\FaviconInterface $favicon, - Title $title + Title $title, + \Magento\Framework\Locale\ResolverInterface $localeResolver ) { $this->assetRepo = $assetRepo; $this->pageAssets = $pageAssets; $this->scopeConfig = $scopeConfig; $this->favicon = $favicon; $this->title = $title; + $this->localeResolver = $localeResolver; + $this->setElementAttribute( + self::ELEMENT_TYPE_HTML, + self::HTML_ATTRIBUTE_LANG, + str_replace('_', '-', $this->localeResolver->getLocale()) + ); } /** diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/Bundle/ManagerTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/Bundle/ManagerTest.php index 30d80e64d0ac96796e7c301a42913b19d8f630ed..840ed49c995b67167b3d4f70a88a9a35e6b0eab3 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/Bundle/ManagerTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/Bundle/ManagerTest.php @@ -9,7 +9,7 @@ use Magento\Framework\View\Asset\Bundle\Manager; class ManagerTest extends \PHPUnit_Framework_TestCase { - /** @var \Magento\Framework\View\Asset\Bundle\Manager|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Manager|\PHPUnit_Framework_MockObject_MockObject */ protected $manager; /** @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject */ @@ -91,55 +91,73 @@ class ManagerTest extends \PHPUnit_Framework_TestCase public function testAddAssetWithExcludedFile() { + $dirRead = $this->getMockBuilder('Magento\Framework\Filesystem\Directory\ReadInterface') + ->disableOriginalConstructor() + ->getMock(); $context = $this->getMockBuilder('Magento\Framework\View\Asset\File\FallbackContext') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); $configView = $this->getMockBuilder('Magento\Framework\Config\View') - ->disableOriginalConstructor() - ->getMock(); + ->setMockClassName('configView') + ->disableOriginalConstructor() + ->getMock(); + $this->asset->expects($this->atLeastOnce()) + ->method('getContext') + ->willReturn($context); $this->asset->expects($this->atLeastOnce()) ->method('getContentType') ->willReturn('js'); - $this->asset->expects($this->once()) - ->method('getSourceFile') - ->willReturn('/source/file'); - $this->asset->expects($this->once()) - ->method('getModule') - ->willReturn(''); $this->asset->expects($this->atLeastOnce()) - ->method('getFilePath') - ->willReturn('file/path.js'); + ->method('getModule') + ->willReturn('Lib'); $this->asset->expects($this->atLeastOnce()) - ->method('getContext') - ->willReturn($context); - $this->bundleConfig - ->expects($this->atLeastOnce()) + ->method('getSourceFile') + ->willReturn('source/file.min.js'); + $this->filesystem->expects($this->once()) + ->method('getDirectoryRead') + ->with(\Magento\Framework\App\Filesystem\DirectoryList::APP) + ->willReturn($dirRead); + $this->bundleConfig->expects($this->atLeastOnce()) ->method('getConfig') ->with($context) ->willReturn($configView); $configView->expects($this->once()) ->method('getExcludedFiles') - ->willReturn(['Lib:' . ':file/path.js']); + ->willReturn(['Lib:' . ':source/file.min.js']); $this->assertFalse($this->manager->addAsset($this->asset)); } public function testAddAssetWithExcludedDirectory() { + $dirRead = $this->getMockBuilder('Magento\Framework\Filesystem\Directory\ReadInterface') + ->disableOriginalConstructor() + ->getMock(); $context = $this->getMockBuilder('Magento\Framework\View\Asset\File\FallbackContext') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); $configView = $this->getMockBuilder('Magento\Framework\Config\View') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem->expects($this->once()) + ->method('getDirectoryRead') + ->with(\Magento\Framework\App\Filesystem\DirectoryList::APP) + ->willReturn($dirRead); + $dirRead->expects($this->once()) + ->method('getAbsolutePath') + ->with('/path/to/file.js') + ->willReturn(true); + $this->asset->expects($this->atLeastOnce()) + ->method('getSourceFile') + ->willReturn('/path/to/source/file.min.js'); $this->asset->expects($this->atLeastOnce()) ->method('getContentType') ->willReturn('js'); $this->asset->expects($this->once()) - ->method('getSourceFile') - ->willReturn('/source/file'); + ->method('getPath') + ->willReturn('/path/to/file.js'); $this->asset->expects($this->atLeastOnce()) ->method('getModule') ->willReturn(''); @@ -149,8 +167,7 @@ class ManagerTest extends \PHPUnit_Framework_TestCase $this->asset->expects($this->atLeastOnce()) ->method('getContext') ->willReturn($context); - $this->bundleConfig - ->expects($this->atLeastOnce()) + $this->bundleConfig->expects($this->atLeastOnce()) ->method('getConfig') ->with($context) ->willReturn($configView); @@ -166,6 +183,9 @@ class ManagerTest extends \PHPUnit_Framework_TestCase public function testAddAsset() { + $dirRead = $this->getMockBuilder('Magento\Framework\Filesystem\Directory\ReadInterface') + ->disableOriginalConstructor() + ->getMock(); $context = $this->getMockBuilder('Magento\Framework\View\Asset\File\FallbackContext') ->disableOriginalConstructor() ->getMock(); @@ -173,20 +193,23 @@ class ManagerTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); + $this->filesystem->expects($this->once()) + ->method('getDirectoryRead') + ->with(\Magento\Framework\App\Filesystem\DirectoryList::APP) + ->willReturn($dirRead); + $this->asset->expects($this->atLeastOnce()) + ->method('getSourceFile') + ->willReturn('/path/to/source/file.min.js'); $this->asset->expects($this->atLeastOnce()) ->method('getContentType') ->willReturn('js'); $this->asset->expects($this->once()) - ->method('getSourceFile') - ->willReturn('/source/file'); - $this->asset->expects($this->atLeastOnce()) - ->method('getFilePath') - ->willReturn('file/path.js'); + ->method('getPath') + ->willReturn('/path/to/file.js'); $this->asset->expects($this->atLeastOnce()) ->method('getContext') ->willReturn($context); - $this->bundleConfig - ->expects($this->atLeastOnce()) + $this->bundleConfig->expects($this->atLeastOnce()) ->method('getConfig') ->with($context) ->willReturn($configView); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php index 128081901a5c97d0cee7f901429ac1cf6466feb5..8e72abcf4189f80230cd40457d9287df482e553e 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/FileTest.php @@ -6,7 +6,7 @@ namespace Magento\Framework\View\Test\Unit\Asset; -use \Magento\Framework\View\Asset\File; +use Magento\Framework\View\Asset\File; class FileTest extends \PHPUnit_Framework_TestCase { diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php index ae61cf0509216bda474b126185b0cfcb112afa37..3519062bbc3ecb27d1ff09a4c71a043e1a3db47e 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php @@ -9,10 +9,13 @@ */ namespace Magento\Framework\View\Test\Unit\Page; +use Magento\Framework\Locale\Resolver; +use Magento\Framework\View\Page\Config; + class ConfigTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Framework\View\Page\Config + * @var Config */ protected $model; @@ -66,6 +69,10 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $this->asset = $this->getMock('Magento\Framework\View\Asset\File', [], [], '', false); $this->remoteAsset = $this->getMock('\Magento\Framework\View\Asset\Remote', [], [], '', false); $this->title = $this->getMock('Magento\Framework\View\Page\Title', [], [], '', false); + $locale = $this->getMockForAbstractClass('Magento\Framework\Locale\ResolverInterface', [], '', false); + $locale->expects($this->any()) + ->method('getLocale') + ->willReturn(Resolver::DEFAULT_LOCALE); $this->model = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this)) ->getObject( 'Magento\Framework\View\Page\Config', @@ -73,7 +80,8 @@ class ConfigTest extends \PHPUnit_Framework_TestCase 'assetRepo' => $this->assetRepo, 'pageAssets' => $this->pageAssets, 'scopeConfig' => $this->scopeConfig, - 'favicon' => $this->favicon + 'favicon' => $this->favicon, + 'localeResolver' => $locale, ] ); } @@ -338,16 +346,16 @@ class ConfigTest extends \PHPUnit_Framework_TestCase 'class', 'test', ], - [ - 'html', - 'context', - 'value' - ], [ 'body', 'class', 'value' - ] + ], + [ + Config::ELEMENT_TYPE_HTML, + Config::HTML_ATTRIBUTE_LANG, + str_replace('_', '-', Resolver::DEFAULT_LOCALE) + ], ]; } @@ -387,15 +395,29 @@ class ConfigTest extends \PHPUnit_Framework_TestCase /** * @param string $elementType - * @param string $attribute - * @param string $value + * @param string $attributes * - * @dataProvider elementAttributeDataProvider + * @dataProvider elementAttributesDataProvider */ - public function testElementAttributes($elementType, $attribute, $value) + public function testElementAttributes($elementType, $attributes) { - $this->model->setElementAttribute($elementType, $attribute, $value); - $this->assertEquals([$attribute => $value], $this->model->getElementAttributes($elementType)); + foreach ($attributes as $attribute => $value) { + $this->model->setElementAttribute($elementType, $attribute, $value); + } + $this->assertEquals($attributes, $this->model->getElementAttributes($elementType)); + } + + public function elementAttributesDataProvider() + { + return [ + [ + 'html', + [ + 'context' => 'value', + Config::HTML_ATTRIBUTE_LANG => str_replace('_', '-', Resolver::DEFAULT_LOCALE) + ], + ], + ]; } /** diff --git a/lib/web/mage/backend/validation.js b/lib/web/mage/backend/validation.js index ec400a5c124f5f00e6146a5f61940f8a750b0a87..122a8159f5863a611742e59c50eeb7a3184ae18b 100644 --- a/lib/web/mage/backend/validation.js +++ b/lib/web/mage/backend/validation.js @@ -34,6 +34,31 @@ // ignore IE throwing errors when focusing hidden elements } } + }, + elements: function () { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + return $(this.currentForm) + .find("input, select, textarea") + .not(this.settings.forceIgnore) + .not(':submit, :reset, :image, [disabled]') + .not(this.settings.ignore) + .filter(function () { + if (!this.name && validator.settings.debug && window.console) { + console.error('%o has no name assigned', this); + } + + // select only the first element for each name, and only those with rules specified + if (this.name in rulesCache || !validator.objectLength($(this).rules())) { + return false; + } + + rulesCache[this.name] = true; + + return true; + }); } }); @@ -62,6 +87,7 @@ $.widget("mage.validation", $.mage.validation, { options: { messagesId: 'messages', + forceIgnore: '', ignore: ':disabled, .ignore-validate, .no-display.template, ' + ':disabled input, .ignore-validate input, .no-display.template input, ' + ':disabled select, .ignore-validate select, .no-display.template select, ' + diff --git a/lib/web/mage/collapsible.js b/lib/web/mage/collapsible.js index 778884fe4791ec56928c63f18b9a622d533e1252..446a25f80ad32343d85c0da4d7b06221597461b1 100644 --- a/lib/web/mage/collapsible.js +++ b/lib/web/mage/collapsible.js @@ -60,6 +60,7 @@ define([ } this._bind("click"); + this._trigger('created'); }, _refresh: function () { @@ -352,7 +353,7 @@ define([ 'aria-hidden': 'true' }); - this.element.trigger('dimensionsChanged'); + this.element.trigger('dimensionsChanged', {opened: false}); }, activate: function () { @@ -409,7 +410,7 @@ define([ 'aria-hidden': 'false' }); - this.element.trigger('dimensionsChanged'); + this.element.trigger('dimensionsChanged', {opened: true}); }, _loadContent: function () { diff --git a/nginx.conf.sample b/nginx.conf.sample index 596c08612b2c0e0543077ec84d5461c5d83d7ed3..137ef8205deda016379d2b4a2abd4f103f8f9882 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -29,13 +29,32 @@ add_header 'X-XSS-Protection' '1; mode=block'; location /setup { root $MAGE_ROOT; - location ~ ^/setup/index.php { fastcgi_pass fastcgi_backend; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } + + location ~ /setup/(?!pub/). { + deny all; + } +} + +location /update { + root $MAGE_ROOT; + + location ~ /update/index.php { + fastcgi_pass fastcgi_backend; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + # deny everything but index.php + location ~ /update/(?!pub/). { + deny all; + } } location / { @@ -43,6 +62,9 @@ location / { } location /pub { + location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { + deny all; + } alias $MAGE_ROOT/pub; } @@ -73,6 +95,11 @@ location /static/ { location /media/ { try_files $uri $uri/ /get.php?$args; + + location ~ ^/media/theme_customization/.*\.xml { + deny all; + } + location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; expires +1y; @@ -93,15 +120,7 @@ location /media/downloadable/ { deny all; } -location ~ /media/theme_customization/.*\.xml$ { - deny all; -} - -location /errors/ { - try_files $uri =404; -} - -location ~ ^/errors/.*\.(xml|phtml)$ { +location /media/import/ { deny all; } diff --git a/package.json b/package.json index 54a730729fb3be86a4d8db13c15cea36367ff409..8869196abeffc1653e34681226e7293a9c71c179 100644 --- a/package.json +++ b/package.json @@ -21,15 +21,14 @@ "grunt-contrib-jasmine": "^0.8.1", "grunt-contrib-less": "^0.12.0", "grunt-contrib-watch": "^0.6.1", - "grunt-eslint": "^16.0.0", + "grunt-eslint": "^17.0.0", "grunt-exec": "^0.4.6", - "grunt-jscs": "^1.8.0", + "grunt-jscs": "^2.1.0", "grunt-replace": "^0.9.2", "grunt-styledocco": "^0.1.4", "grunt-template-jasmine-requirejs": "^0.2.3", "grunt-text-replace": "^0.4.0", "imagemin-svgo": "^4.0.1", - "jscs-jsdoc": "^1.1.0", "load-grunt-config": "^0.16.0", "morgan": "^1.5.0", "node-minify": "^1.0.1", diff --git a/pub/errors/.htaccess b/pub/errors/.htaccess index 5a3f0a15d124ed9f66b3fd0e97ad4d6ae7cce0f7..3692dd439e2ff90b141f4e2c362518e359e7079e 100644 --- a/pub/errors/.htaccess +++ b/pub/errors/.htaccess @@ -2,6 +2,3 @@ Options None <IfModule mod_rewrite.c> RewriteEngine Off </IfModule> -<FilesMatch "\.(xml|phtml)$"> - Deny from all -</FilesMatch> \ No newline at end of file diff --git a/setup/config/.htaccess b/setup/config/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..281d5c33db37cd1cc887dbb2d36897b897835071 --- /dev/null +++ b/setup/config/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/setup/performance-toolkit/.htaccess b/setup/performance-toolkit/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..281d5c33db37cd1cc887dbb2d36897b897835071 --- /dev/null +++ b/setup/performance-toolkit/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index bd6658516e1c3065251cdeadfaaec2fcd4f97532..45bd1d57a1e6bb44efcbea4d81a98f7ea9422134 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -447,7 +447,7 @@ if (1 == Integer.parseInt(vars.get("simple_products_counter"))) { // Create product map Map productMap = new HashMap(); -productMap.put("id", vars.get("simple_product_id")); +productMap.put("id", vars.get("simple_product_id")); productMap.put("title", vars.get("simple_product_title")); productMap.put("url_key", vars.get("simple_products_url_key")); @@ -797,7 +797,7 @@ productList.add(productMap); </stringProp> </hashTree> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="Admin - Prepare Customers search Data" enabled="true"> <stringProp name="BeanShellSampler.query">import org.apache.jmeter.protocol.http.util.Base64Encoder; -String searchData = "customer_since[locale]=en_US&website_id=1"; +String searchData = "customer_since[locale]=en_US&website_id=1"; vars.put("searchData", new String(Base64Encoder.encode(searchData)));</stringProp> <stringProp name="BeanShellSampler.filename"></stringProp> <stringProp name="BeanShellSampler.parameters"></stringProp> @@ -2433,11 +2433,11 @@ vars.put("category_name", props.get("category_name"));</stri </elementProp> <elementProp name="Content-Type" elementType="Header"> <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json; charset=UTF-8
</stringProp> + <stringProp name="Header.value">application/json; charset=UTF-8</stringProp> </elementProp> <elementProp name="X-Requested-With" elementType="Header"> <stringProp name="Header.name">X-Requested-With</stringProp> - <stringProp name="Header.value">XMLHttpRequest
</stringProp> + <stringProp name="Header.value">XMLHttpRequest</stringProp> </elementProp> <elementProp name="Accept" elementType="Header"> <stringProp name="Header.name">Accept</stringProp> @@ -2623,7 +2623,7 @@ print(emailsCount); if (emailsCount < 1) { log.error("You have to increase customers qty for running 'Customer Checkout' thread."); System.out.println("You have to increase customers qty for running 'Customer Checkout' thread."); - SampleResult.setStopTest(true); + SampleResult.setStopTest(true); } else { emails = props.get("customer_emails_list"); email = emails.get(emailsCount - 1); @@ -3297,11 +3297,11 @@ if (emailsCount < 1) { </elementProp> <elementProp name="Content-Type" elementType="Header"> <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json; charset=UTF-8
</stringProp> + <stringProp name="Header.value">application/json; charset=UTF-8</stringProp> </elementProp> <elementProp name="X-Requested-With" elementType="Header"> <stringProp name="Header.name">X-Requested-With</stringProp> - <stringProp name="Header.value">XMLHttpRequest
</stringProp> + <stringProp name="Header.value">XMLHttpRequest</stringProp> </elementProp> <elementProp name="Accept" elementType="Header"> <stringProp name="Header.name">Accept</stringProp> diff --git a/setup/src/.htaccess b/setup/src/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..281d5c33db37cd1cc887dbb2d36897b897835071 --- /dev/null +++ b/setup/src/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php index 707c8ee9dae3296836b2a16a000298fded1c9359..05a46aac47dade5b0a8f9b40ad7eeb16ea175388 100644 --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -8,6 +8,7 @@ namespace Magento\Setup\Console\Command; use Magento\Framework\Filesystem; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Setup\Model\ObjectManagerProvider; @@ -44,6 +45,9 @@ class DiCompileCommand extends Command /** @var array */ private $excludedPathsList; + /** @var DriverInterface */ + private $fileDriver; + /** * Constructor * @@ -52,19 +56,22 @@ class DiCompileCommand extends Command * @param Manager $taskManager * @param ObjectManagerProvider $objectManagerProvider * @param Filesystem $filesystem + * @param DriverInterface $fileDriver */ public function __construct( DeploymentConfig $deploymentConfig, DirectoryList $directoryList, Manager $taskManager, ObjectManagerProvider $objectManagerProvider, - Filesystem $filesystem + Filesystem $filesystem, + DriverInterface $fileDriver ) { $this->deploymentConfig = $deploymentConfig; $this->directoryList = $directoryList; $this->objectManager = $objectManagerProvider->get(); $this->taskManager = $taskManager; $this->filesystem = $filesystem; + $this->fileDriver = $fileDriver; parent::__construct(); } @@ -80,18 +87,50 @@ class DiCompileCommand extends Command parent::configure(); } + /** + * Checks that application is installed and DI resources are cleared + * + * @return string[] + */ + private function checkEnvironment() + { + $messages = []; + if (!$this->deploymentConfig->isAvailable()) { + $messages[] = 'You cannot run this command because the Magento application is not installed.'; + } + + /** + * By the time the command is able to execute, the Object Management configuration is already contaminated + * by old config info, and it's too late to just clear the files in code. + * + * TODO: reconfigure OM in runtime so DI resources can be cleared after command launches + * + */ + $path = $this->directoryList->getPath(DirectoryList::DI); + if ($this->fileDriver->isExists($path)) { + $messages[] = "DI configuration must be cleared before running compiler. Please delete '$path'."; + } + + return $messages; + } + /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { + $errors = $this->checkEnvironment(); + if ($errors) { + foreach ($errors as $line) { + $output->writeln($line); + } + return; + } + $appCodePath = $this->directoryList->getPath(DirectoryList::MODULES); $libraryPath = $this->directoryList->getPath(DirectoryList::LIB_INTERNAL); $generationPath = $this->directoryList->getPath(DirectoryList::GENERATION); - if (!$this->deploymentConfig->isAvailable()) { - $output->writeln('You cannot run this command because the Magento application is not installed.'); - return; - } + $this->objectManager->get('Magento\Framework\App\Cache')->clean(); $compiledPathsList = [ 'application' => $appCodePath, diff --git a/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php b/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php index a5b6ad9b38dcf3596389150c0f3a08adca5cf36e..91287935742652a91c2a80e843352081cdf63c5e 100644 --- a/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php +++ b/setup/src/Magento/Setup/Fixtures/ConfigurableProductsFixture.php @@ -124,9 +124,6 @@ class ConfigurableProductsFixture extends Fixture '_tier_price_customer_group', '_tier_price_qty', '_tier_price_price', - '_group_price_website', - '_group_price_customer_group', - '_group_price_price', '_media_attribute_id', '_media_image', '_media_label', @@ -249,9 +246,6 @@ class ConfigurableProductsFixture extends Fixture '_tier_price_customer_group' => '', '_tier_price_qty' => '', '_tier_price_price' => '', - '_group_price_website' => '', - '_group_price_customer_group' => '', - '_group_price_price' => '', '_media_attribute_id' => '', '_media_image' => '', '_media_label' => '', @@ -358,9 +352,6 @@ class ConfigurableProductsFixture extends Fixture '_tier_price_customer_group' => '', '_tier_price_qty' => '', '_tier_price_price' => '', - '_group_price_website' => '', - '_group_price_customer_group' => '', - '_group_price_price' => '', '_media_attribute_id' => '', '_media_image' => '', '_media_label' => '', @@ -464,9 +455,6 @@ class ConfigurableProductsFixture extends Fixture '_tier_price_customer_group' => '', '_tier_price_qty' => '', '_tier_price_price' => '', - '_group_price_website' => '', - '_group_price_customer_group' => '', - '_group_price_price' => '', '_media_attribute_id' => '', '_media_image' => '', '_media_label' => '', @@ -570,9 +558,6 @@ class ConfigurableProductsFixture extends Fixture '_tier_price_customer_group' => '', '_tier_price_qty' => '', '_tier_price_price' => '', - '_group_price_website' => '', - '_group_price_customer_group' => '', - '_group_price_price' => '', '_media_attribute_id' => '', '_media_image' => '', '_media_label' => '', diff --git a/setup/src/Magento/Setup/Model/AdminAccount.php b/setup/src/Magento/Setup/Model/AdminAccount.php index 5642dda6afb31035d259682e06f7cb078a01cd16..cd5bef2709734e160a9acd717b382080a4e3a305 100644 --- a/setup/src/Magento/Setup/Model/AdminAccount.php +++ b/setup/src/Magento/Setup/Model/AdminAccount.php @@ -9,7 +9,7 @@ namespace Magento\Setup\Model; use Magento\Authorization\Model\Acl\Role\Group; use Magento\Authorization\Model\Acl\Role\User; use Magento\Authorization\Model\UserContextInterface; -use Magento\Framework\Math\Random; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Setup\Module\Setup; class AdminAccount @@ -39,23 +39,24 @@ class AdminAccount private $data; /** - * Random Generator - * - * @var \Magento\Framework\Math\Random + * @var EncryptorInterface */ - private $random; + private $encryptor; /** * Default Constructor * * @param Setup $setup - * @param Random $random + * @param EncryptorInterface $encryptor * @param array $data */ - public function __construct(Setup $setup, Random $random, array $data) - { + public function __construct( + Setup $setup, + EncryptorInterface $encryptor, + array $data + ) { $this->setup = $setup; - $this->random = $random; + $this->encryptor = $encryptor; $this->data = $data; } @@ -66,8 +67,7 @@ class AdminAccount */ protected function generatePassword() { - $salt = $this->random->getRandomString(32); - return md5($salt . $this->data[self::KEY_PASSWORD]) . ':' . $salt; + return $this->encryptor->getHash($this->data[self::KEY_PASSWORD], true); } /** diff --git a/setup/src/Magento/Setup/Model/AdminAccountFactory.php b/setup/src/Magento/Setup/Model/AdminAccountFactory.php index 5e1657ab7679878cf2483e17f23e79b01996f552..8279abdef977f8c8f3c13f5cc35b4d3cae4fc245 100644 --- a/setup/src/Magento/Setup/Model/AdminAccountFactory.php +++ b/setup/src/Magento/Setup/Model/AdminAccountFactory.php @@ -33,7 +33,7 @@ class AdminAccountFactory { return new AdminAccount( $setup, - $this->serviceLocator->get('Magento\Framework\Math\Random'), + $this->serviceLocator->get('Magento\Framework\Encryption\Encryptor'), $data ); } diff --git a/setup/src/Magento/Setup/Module/Di/Compiler/Config/Writer/Filesystem.php b/setup/src/Magento/Setup/Module/Di/Compiler/Config/Writer/Filesystem.php index c90c1582bc57b95e1dbca5aab0bc861490bf564b..3bf843565a3bc2d17ef2823f7a51db277e606bf6 100644 --- a/setup/src/Magento/Setup/Module/Di/Compiler/Config/Writer/Filesystem.php +++ b/setup/src/Magento/Setup/Module/Di/Compiler/Config/Writer/Filesystem.php @@ -8,6 +8,7 @@ namespace Magento\Setup\Module\Di\Compiler\Config\Writer; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\DriverInterface; use Magento\Setup\Module\Di\Compiler\Config\WriterInterface; class Filesystem implements WriterInterface @@ -50,7 +51,7 @@ class Filesystem implements WriterInterface private function initialize() { if (!file_exists($this->directoryList->getPath(DirectoryList::DI))) { - mkdir($this->directoryList->getPath(DirectoryList::DI)); + mkdir($this->directoryList->getPath(DirectoryList::DI), DriverInterface::WRITEABLE_DIRECTORY_MODE); } } } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php index 2b9381b4783a495e0a179e6221734c0ac34e5895..205e0799436bb77e81fcda0bfed630df89874c55 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php @@ -28,6 +28,12 @@ class DiCompileCommandTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject */ private $filesystem; + /** @var \Magento\Framework\Filesystem\Driver\File | \PHPUnit_Framework_MockObject_MockObject*/ + private $fileDriver; + + /** @var \Magento\Framework\App\Filesystem\DirectoryList | \PHPUnit_Framework_MockObject_MockObject*/ + private $directoryList; + public function setUp() { $this->deploymentConfig = $this->getMock('Magento\Framework\App\DeploymentConfig', [], [], '', false); @@ -52,23 +58,39 @@ class DiCompileCommandTest extends \PHPUnit_Framework_TestCase ->method('get') ->willReturn($this->objectManager); $this->manager = $this->getMock('Magento\Setup\Module\Di\App\Task\Manager', [], [], '', false); - $directoryList = $this->getMock('Magento\Framework\App\Filesystem\DirectoryList', [], [], '', false); + $this->directoryList = $this->getMock('Magento\Framework\App\Filesystem\DirectoryList', [], [], '', false); $this->filesystem = $this->getMockBuilder('Magento\Framework\Filesystem') ->disableOriginalConstructor() ->getMock(); - $directoryList->expects($this->exactly(3))->method('getPath'); + $this->fileDriver = $this->getMockBuilder('Magento\Framework\Filesystem\Driver\File') + ->disableOriginalConstructor() + ->getMock(); + $this->command = new DiCompileCommand( $this->deploymentConfig, - $directoryList, + $this->directoryList, $this->manager, $objectManagerProvider, - $this->filesystem + $this->filesystem, + $this->fileDriver ); } + public function testExecuteDiExists() + { + $diPath = '/root/magento/var/di'; + $this->deploymentConfig->expects($this->once())->method('isAvailable')->willReturn(true); + $this->fileDriver->expects($this->atLeastOnce())->method('isExists')->with($diPath)->willReturn(true); + $this->directoryList->expects($this->atLeastOnce())->method('getPath')->willReturn($diPath); + $tester = new CommandTester($this->command); + $tester->execute([]); + $this->assertContains("delete '/root/magento/var/di'", $tester->getDisplay()); + } + public function testExecuteNotInstalled() { + $this->directoryList->expects($this->atLeastOnce())->method('getPath')->willReturn(null); $this->deploymentConfig->expects($this->once())->method('isAvailable')->willReturn(false); $tester = new CommandTester($this->command); $tester->execute([]); @@ -80,6 +102,7 @@ class DiCompileCommandTest extends \PHPUnit_Framework_TestCase public function testExecute() { + $this->directoryList->expects($this->atLeastOnce())->method('getPath')->willReturn(null); $this->objectManager->expects($this->once()) ->method('get') ->with('Magento\Framework\App\Cache') diff --git a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php index 66dbae8cdbcb9230393f6204f7665871c7c7d02e..aaebe00051013423517f625fd345765e378e4395 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php @@ -16,8 +16,8 @@ class AdminAccountFactoryTest extends \PHPUnit_Framework_TestCase $serviceLocatorMock ->expects($this->once()) ->method('get') - ->with('Magento\Framework\Math\Random') - ->will($this->returnValue($this->getMock('Magento\Framework\Math\Random'))); + ->with('Magento\Framework\Encryption\Encryptor') + ->willReturn($this->getMockForAbstractClass('Magento\Framework\Encryption\EncryptorInterface')); $adminAccountFactory = new AdminAccountFactory($serviceLocatorMock); $adminAccount = $adminAccountFactory->create( $this->getMock('Magento\Setup\Module\Setup', [], [], '', false), diff --git a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountTest.php b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountTest.php index 598641f1b844c84309a107b93c109ba0a88ab3d9..214ce1fcf1a3e824921030ac69fa251214d2b9ca 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountTest.php @@ -21,9 +21,9 @@ class AdminAccountTest extends \PHPUnit_Framework_TestCase private $dbAdapterMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Math\Random + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Encryption\EncryptorInterface */ - private $randomMock; + private $encryptor; /** * @var AdminAccount @@ -50,8 +50,8 @@ class AdminAccountTest extends \PHPUnit_Framework_TestCase } )); - $this->randomMock = $this->getMock('Magento\Framework\Math\Random'); - $this->randomMock->expects($this->any())->method('getRandomString')->will($this->returnValue('salt')); + $this->encryptor = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface') + ->getMockForAbstractClass(); $data = [ AdminAccount::KEY_FIRST_NAME => 'John', @@ -61,7 +61,7 @@ class AdminAccountTest extends \PHPUnit_Framework_TestCase AdminAccount::KEY_USER => 'admin', ]; - $this->adminAccount = new AdminAccount($this->setUpMock, $this->randomMock, $data); + $this->adminAccount = new AdminAccount($this->setUpMock, $this->encryptor, $data); } public function testSaveUserExistsAdminRoleExists() diff --git a/setup/view/.htaccess b/setup/view/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..281d5c33db37cd1cc887dbb2d36897b897835071 --- /dev/null +++ b/setup/view/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/vendor/.htaccess b/vendor/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..cb24fd7fc0b3a0e46f6cad19e834117476bd0341 --- /dev/null +++ b/vendor/.htaccess @@ -0,0 +1,2 @@ +Order allow,deny +Deny from all