diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 12ad4e452b1..2b1720ccaab 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,11 +5,12 @@ - Information on your environment, - Steps to reproduce, - Expected and actual results, + Fields marked with (*) are required. Please don't remove the template. Please also have a look at our guidelines article before adding a new issue https://github.com/magento/magento2/wiki/Issue-reporting-guidelines --> -### Preconditions +### Preconditions (*) 1. [Screenshots, logs or description] -### Actual result +### Actual result (*) 1. [Screenshots, logs or description] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 17aa66c919e..33a6ef02ace 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,28 +6,29 @@ about: Technical issue with the Magento 2 core components -### Preconditions +### Preconditions (*) 1. 2. -### Steps to reproduce +### Steps to reproduce (*) 1. 2. -### Expected result +### Expected result (*) 1. [Screenshots, logs or description] 2. -### Actual result +### Actual result (*) 1. [Screenshots, logs or description] 2. diff --git a/.github/ISSUE_TEMPLATE/developer-experience-issue.md b/.github/ISSUE_TEMPLATE/developer-experience-issue.md index a66b0c62ef8..423d4818fb3 100644 --- a/.github/ISSUE_TEMPLATE/developer-experience-issue.md +++ b/.github/ISSUE_TEMPLATE/developer-experience-issue.md @@ -6,12 +6,13 @@ about: Issues related to customization, extensibility, modularity -### Summary +### Summary (*) -### Examples +### Examples (*) ### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index de85da43b70..f64185773ca 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,13 +5,13 @@ about: Please consider reporting directly to https://github.com/magento/communit --- -### Description +### Description (*) -### Expected behavior +### Expected behavior (*) ### Benefits diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5b0b9d74e45..f191bd9aaba 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,12 +3,13 @@ To help us process this pull request we recommend that you add the following information: - Summary of the pull request, - Issue(s) related to the changes made, - - Manual testing scenarios, + - Manual testing scenarios + Fields marked with (*) are required. Please don't remove the template. --> -### Description +### Description (*) diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php index 41f11b3d529..62a2fa1c47e 100644 --- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php +++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php @@ -188,6 +188,10 @@ public function getJsonConfig() $configValue = $preConfiguredValues->getData('bundle_option/' . $optionId); if ($configValue) { $defaultValues[$optionId] = $configValue; + $configQty = $preConfiguredValues->getData('bundle_option_qty/' . $optionId); + if ($configQty) { + $options[$optionId]['selections'][$configValue]['qty'] = $configQty; + } } } $position++; diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index a2a02cbd715..92bada8094c 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -6,13 +6,13 @@ namespace Magento\Bundle\Model\Product; -use Magento\Framework\App\ObjectManager; +use Magento\Bundle\Model\ResourceModel\Selection\Collection as Selections; +use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier as SelectionCollectionFilterApplier; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier as SelectionCollectionFilterApplier; -use Magento\Bundle\Model\ResourceModel\Selection\Collection as Selections; /** * Bundle Type Model @@ -537,7 +537,7 @@ public function updateQtyOption($options, \Magento\Framework\DataObject $option, foreach ($options as $quoteItemOption) { if ($quoteItemOption->getCode() == 'selection_qty_' . $selection->getSelectionId()) { if ($optionUpdateFlag) { - $quoteItemOption->setValue(intval($quoteItemOption->getValue())); + $quoteItemOption->setValue((int) $quoteItemOption->getValue()); } else { $quoteItemOption->setValue($value); } @@ -625,6 +625,7 @@ public function isSalable($product) /** * Prepare product and its configuration to be added to some products list. + * * Perform standard preparation process and then prepare of bundle selections options. * * @param \Magento\Framework\DataObject $buyRequest @@ -790,6 +791,8 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p } /** + * Cast array values to int + * * @param array $array * @return int[]|int[][] */ @@ -809,6 +812,8 @@ private function recursiveIntval(array $array) } /** + * Convert multi dimensional array to flat + * * @param array $array * @return int[] */ @@ -920,8 +925,7 @@ public function getOptionsByIds($optionIds, $product) } /** - * Prepare additional options/information for order item which will be - * created from this product + * Prepare additional options/information for order item which will be created from this product * * @param \Magento\Catalog\Model\Product $product * @return array @@ -987,6 +991,7 @@ public function getOrderOptions($product) /** * Sort selections method for usort function + * * Sort selections by option position, selection position and selection id * * @param \Magento\Catalog\Model\Product $firstItem @@ -1009,10 +1014,8 @@ public function shakeSelections($firstItem, $secondItem) $secondItem->getPosition(), $secondItem->getSelectionId(), ]; - if ($aPosition == $bPosition) { - return 0; - } - return $aPosition < $bPosition ? -1 : 1; + + return $aPosition <=> $bPosition; } /** @@ -1050,6 +1053,7 @@ public function getForceChildItemQtyChanges($product) /** * Retrieve additional searchable data from type instance + * * Using based on product id and store_id data * * @param \Magento\Catalog\Model\Product $product @@ -1118,6 +1122,7 @@ public function checkProductBuyState($product) /** * Retrieve products divided into groups required to purchase + * * At least one product in each group has to be purchased * * @param \Magento\Catalog\Model\Product $product @@ -1214,6 +1219,8 @@ public function getIdentities(\Magento\Catalog\Model\Product $product) } /** + * Returns selection qty + * * @param \Magento\Framework\DataObject $selection * @param int[] $qtys * @param int $selectionOptionId @@ -1232,6 +1239,8 @@ protected function getQty($selection, $qtys, $selectionOptionId) } /** + * Returns qty + * * @param \Magento\Catalog\Model\Product $product * @param \Magento\Framework\DataObject $selection * @return float|int @@ -1249,6 +1258,8 @@ protected function getBeforeQty($product, $selection) } /** + * Validate required options + * * @param \Magento\Catalog\Model\Product $product * @param bool $isStrictProcessMode * @param \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection @@ -1270,6 +1281,8 @@ protected function checkIsAllRequiredOptions($product, $isStrictProcessMode, $op } /** + * Check if selection is salable + * * @param \Magento\Bundle\Model\ResourceModel\Selection\Collection $selections * @param bool $skipSaleableCheck * @param \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection @@ -1300,6 +1313,8 @@ protected function checkSelectionsIsSale($selections, $skipSaleableCheck, $optio } /** + * Validate result + * * @param array $_result * @return void * @throws \Magento\Framework\Exception\LocalizedException @@ -1318,6 +1333,8 @@ protected function checkIsResult($_result) } /** + * Merge selections with options + * * @param \Magento\Catalog\Model\Product\Option[] $options * @param \Magento\Framework\DataObject[] $selections * @return \Magento\Framework\DataObject[] diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml new file mode 100644 index 00000000000..ad9a8253e91 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + 4.99 + + + 2.89 + + + 7.33 + + + 18.25 + + + + {{productName}} + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.99 + + + 2.89 + + + 7.33 + + + 18.25 + + + + {{productName}} + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml index 6e889ea2432..72e72911194 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml @@ -15,11 +15,12 @@ - - + + + - + diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml index af93200f946..9dc30e73228 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml @@ -60,4 +60,19 @@ CustomAttributeDynamicPrice CustomAttributePriceViewRange + + Api Fixed Bundle Product + api-fixed-bundle-product + bundle + 4 + 1.23 + 4 + 1 + api-fixed-bundle-product + EavStockItem + ApiProductDescription + ApiProductShortDescription + CustomAttributeFixPrice + CustomAttributePriceView + diff --git a/app/code/Magento/Bundle/etc/db_schema.xml b/app/code/Magento/Bundle/etc/db_schema.xml index 8092f34c533..f1c1e7ae782 100644 --- a/app/code/Magento/Bundle/etc/db_schema.xml +++ b/app/code/Magento/Bundle/etc/db_schema.xml @@ -120,11 +120,11 @@ + comment="Entity ID"/> + comment="Customer Group ID"/> + comment="Entity ID"/> + comment="Website ID"/> + comment="Stock ID"/> + comment="Entity ID"/> + comment="Website ID"/> + default="0" comment="Tax Class ID"/> + comment="Entity ID"/> + comment="Website ID"/> + default="0" comment="Tax Class ID"/> + comment="Entity ID"/> + comment="Website ID"/> + comment="Entity ID"/> + comment="Website ID"/> + comment="Entity ID"/> + comment="Website ID"/> + comment="Entity ID"/> + comment="Website ID"/> 'delete', 'label' => __('Delete'), - 'on_click' => "categoryDelete('" . $this->getDeleteUrl() . "')", + 'on_click' => "deleteConfirm('" .__('Are you sure you want to delete this category?') ."', '" + . $this->getDeleteUrl() . "', {data: {}})", 'class' => 'delete', 'sort_order' => 10 ]; 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 4310f61c571..a08142c10be 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 @@ -9,13 +9,18 @@ * * @author Magento Core Team */ +declare(strict_types=1); + namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Action\Attribute\Tab; use Magento\Framework\Data\Form\Element\AbstractElement; /** + * Attributes tab block + * * @api * @SuppressWarnings(PHPMD.DepthOfInheritance) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements @@ -31,6 +36,9 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements */ protected $_attributeAction; + /** @var array */ + private $excludeFields; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Registry $registry @@ -38,6 +46,7 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements * @param \Magento\Catalog\Model\ProductFactory $productFactory * @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeAction * @param array $data + * @param array|null $excludeFields */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -45,14 +54,19 @@ public function __construct( \Magento\Framework\Data\FormFactory $formFactory, \Magento\Catalog\Model\ProductFactory $productFactory, \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeAction, - array $data = [] + array $data = [], + array $excludeFields = null ) { $this->_attributeAction = $attributeAction; $this->_productFactory = $productFactory; + $this->excludeFields = $excludeFields ?: []; + parent::__construct($context, $registry, $formFactory, $data); } /** + * Construct block + * * @return void */ protected function _construct() @@ -62,20 +76,14 @@ protected function _construct() } /** + * Prepares form + * * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _prepareForm() + protected function _prepareForm(): void { - $this->setFormExcludedFieldList( - [ - 'category_ids', - 'gallery', - 'image', - 'media_gallery', - 'quantity_and_stock_status', - 'tier_price', - ] - ); + $this->setFormExcludedFieldList($this->getExcludedFields()); $this->_eventManager->dispatch( 'adminhtml_catalog_product_form_prepare_excluded_field_list', ['object' => $this] @@ -149,12 +157,14 @@ protected function _getAdditionalElementHtml($element) weightHandle.hideWeightSwitcher(); }); HTML; - // @codingStandardsIgnoreEnd + // @codingStandardsIgnoreEnd } return $html; } /** + * Returns tab label + * * @return \Magento\Framework\Phrase */ public function getTabLabel() @@ -163,6 +173,8 @@ public function getTabLabel() } /** + * Return Tab title + * * @return \Magento\Framework\Phrase */ public function getTabTitle() @@ -171,6 +183,8 @@ public function getTabTitle() } /** + * Can show tab in tabs + * * @return bool */ public function canShowTab() @@ -179,10 +193,22 @@ public function canShowTab() } /** + * Tab not hidden + * * @return bool */ public function isHidden() { return false; } + + /** + * Returns excluded fields + * + * @return array + */ + private function getExcludedFields(): array + { + return $this->excludeFields; + } } diff --git a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php index 704271b58f4..b4c24231a74 100644 --- a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php +++ b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php @@ -139,7 +139,7 @@ public function getCacheKeyInfo() [ $this->getDisplayType(), $this->getProductsPerPage(), - intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)), + (int) $this->getRequest()->getParam($this->getData('page_var_name'), 1), $this->serializer->serialize($this->getRequest()->getParams()) ] ); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php index 6456b6d578b..733e270174e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php @@ -6,12 +6,14 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + /** * Class Add Category * * @package Magento\Catalog\Controller\Adminhtml\Category */ -class Add extends \Magento\Catalog\Controller\Adminhtml\Category +class Add extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface { /** * Forward factory for result diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php index b8865f2de8d..752257f5b90 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; -class CategoriesJson extends \Magento\Catalog\Controller\Adminhtml\Category +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class CategoriesJson extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php index 0a54475b15f..39122d139c9 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; -class Delete extends \Magento\Catalog\Controller\Adminhtml\Category +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface { /** * @var \Magento\Catalog\Api\CategoryRepositoryInterface diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php index 6ff478e49a3..0450ff1607a 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; -class Edit extends \Magento\Catalog\Controller\Adminhtml\Category +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php index 4cc0f2d89d1..d1efa0014d4 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php @@ -5,12 +5,13 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category\Image; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; /** * Class Upload */ -class Upload extends \Magento\Backend\App\Action +class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Image uploader @@ -54,14 +55,6 @@ public function execute() try { $result = $this->imageUploader->saveFileToTmpDir($imageId); - - $result['cookie'] = [ - 'name' => $this->_getSession()->getName(), - 'value' => $this->_getSession()->getSessionId(), - 'lifetime' => $this->_getSession()->getCookieLifetime(), - 'path' => $this->_getSession()->getCookiePath(), - 'domain' => $this->_getSession()->getCookieDomain(), - ]; } catch (\Exception $e) { $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php index 902d71775a3..5089b37f90c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; -class Index extends \Magento\Catalog\Controller\Adminhtml\Category +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface { /** * @var \Magento\Backend\Model\View\Result\ForwardFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php index df2c80eda14..ba6bfddca9c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; -class Move extends \Magento\Catalog\Controller\Adminhtml\Category +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Move extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory @@ -26,7 +28,7 @@ class Move extends \Magento\Catalog\Controller\Adminhtml\Category /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory - * @param \Magento\Framework\View\LayoutFactory $layoutFactory, + * @param \Magento\Framework\View\LayoutFactory $layoutFactory * @param \Psr\Log\LoggerInterface $logger */ public function __construct( diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php index 9384397b67f..046ebbb119e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; -class RefreshPath extends \Magento\Catalog\Controller\Adminhtml\Category +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class RefreshPath extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php index aa9ae88754b..11c0a73a737 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Controller\Adminhtml\Category; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Catalog\Api\Data\CategoryAttributeInterface; use Magento\Store\Model\StoreManagerInterface; @@ -14,7 +15,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Catalog\Controller\Adminhtml\Category +class Save extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\RawFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php index 4eda49068ac..66a5fb1008a 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php @@ -5,10 +5,14 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Category; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Catalog\Controller\Adminhtml\Category as CategoryAction; + /** * Catalog category validate */ -class Validate extends \Magento\Catalog\Controller\Adminhtml\Category +class Validate extends CategoryAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php index 7eb391dedf8..3cba09b1e8e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php @@ -6,10 +6,18 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Ui\Component\MassAction\Filter; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute as AttributeAction; -class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute +/** + * Form for mass updatings products' attributes. + * Can be accessed by GET since it's a form, + * can be accessed by POST since it's used as a processor of a mass-action button. + */ +class Edit extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php index 0fbf9054ef1..0730e7a7c5d 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php @@ -6,13 +6,14 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; /** * Class Save * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute +class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute implements HttpPostActionInterface { /** * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php index a873f08d082..30a6629dd1c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php @@ -6,7 +6,11 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute; -class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute as AttributeAction; + +class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php index bef6aee0e2a..faa9e4ddf49 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; -class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php index a99cbdbade1..a41cd71aea4 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; -class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpGetActionInterface { /** * @return \Magento\Framework\Controller\ResultInterface diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php index 9bdc54b289c..34267121f9b 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; -class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php index e954d973059..fdfde7e8060 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; -class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpGetActionInterface { /** * @var \Magento\Backend\Model\View\Result\ForwardFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index 817de6828e4..84ad6d21167 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Backend\Model\View\Result\Redirect; use Magento\Catalog\Api\Data\ProductAttributeInterface; @@ -33,7 +34,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends Attribute +class Save extends Attribute implements HttpPostActionInterface { /** * @var BuildFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php index db452113ada..dac92f9b2eb 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php @@ -7,9 +7,12 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\DataObject; +use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction; -class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute +class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface { const DEFAULT_MESSAGE_KEY = 'message'; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php index 1b9316a95ad..c31ceabcda6 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class Edit extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface { /** * Array of actions which can be processed without secret key validation diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php index b5660ea8793..ff7311e9317 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php @@ -6,9 +6,10 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Gallery; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; -class Upload extends \Magento\Backend\App\Action +class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php index ea66ecf6b16..7755a512eb9 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; -class Index extends \Magento\Catalog\Controller\Adminhtml\Product +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php index f32c6edd573..8fceba3c45e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php @@ -6,13 +6,14 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Catalog\Api\ProductRepositoryInterface; -class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product +class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface { /** * Massactions filter diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php index e3623aabfa1..b7655f7ee28 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php @@ -6,6 +6,7 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Catalog\Controller\Adminhtml\Product; use Magento\Framework\Controller\ResultFactory; @@ -15,7 +16,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class MassStatus extends \Magento\Catalog\Controller\Adminhtml\Product +class MassStatus extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface { /** * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php index 0b027105cd7..0b1ef98c386 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php @@ -6,11 +6,12 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Catalog\Controller\Adminhtml\Product; use Magento\Framework\App\ObjectManager; -class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product +class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface { /** * @var Initialization\StockDataFilter diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php index ff87e7f5741..a0963e60d88 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php @@ -5,12 +5,13 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; /** * Backend reload of product create/edit form */ -class Reload extends \Magento\Catalog\Controller\Adminhtml\Product +class Reload extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface { /** * {@inheritdoc} diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php index 562fe9278e8..e84d9ff1290 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Controller\Adminhtml\Product; @@ -17,7 +18,7 @@ * Class Save * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Catalog\Controller\Adminhtml\Product +class Save extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface { /** * @var Initialization\Helper diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php index 480c30322a0..bfe474abba1 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Set; -class Add extends \Magento\Catalog\Controller\Adminhtml\Product\Set +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Add extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php index f2695311732..771cc83f79e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Set; -class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Set +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpPostActionInterface { /** * @var \Magento\Eav\Api\AttributeSetRepositoryInterface diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php index ec540180b03..6f6870cb084 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Set; -class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Set +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php index 29f7dff4f0d..aadf724f600 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Set; -class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Set +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php index c5dd9ce6d8e..83620de25b0 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php @@ -6,12 +6,13 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Set; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\ObjectManager; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Set +class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpPostActionInterface { /** * @var \Magento\Framework\View\LayoutFactory diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php index e131bfe38c5..77c9cfcd40f 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php @@ -6,6 +6,8 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Catalog\Controller\Adminhtml\Product; use Magento\Framework\App\ObjectManager; @@ -16,7 +18,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Validate extends \Magento\Catalog\Controller\Adminhtml\Product +class Validate extends Product implements HttpPostActionInterface, HttpGetActionInterface { /** * @var \Magento\Framework\Stdlib\DateTime\Filter\Date diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index 226e5725050..19243aabb1b 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -6,15 +6,20 @@ */ namespace Magento\Catalog\Controller\Category; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Catalog\Model\Layer\Resolver; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\View\Result\PageFactory; +use Magento\Framework\App\Action\Action; /** + * View a category on storefront. Needs to be accessible by POST because of the store switching. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class View extends \Magento\Framework\App\Action\Action +class View extends Action implements HttpGetActionInterface, HttpPostActionInterface { /** * Core registry diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php index eb9cc831255..d99901c915a 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php @@ -6,9 +6,10 @@ */ namespace Magento\Catalog\Controller\Product\Compare; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; -class Add extends \Magento\Catalog\Controller\Product\Compare +class Add extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface { /** * Add item to compare list diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php index 568fbf1d056..2703e9869bd 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php @@ -6,9 +6,10 @@ */ namespace Magento\Catalog\Controller\Product\Compare; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class Clear extends \Magento\Catalog\Controller\Product\Compare +class Clear extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface { /** * Remove all items from comparison list diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Index.php b/app/code/Magento/Catalog/Controller/Product/Compare/Index.php index 3eba058318a..c0aa32a56ed 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Index.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Index.php @@ -6,6 +6,7 @@ */ namespace Magento\Catalog\Controller\Product\Compare; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\View\Result\PageFactory; @@ -13,7 +14,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Index extends \Magento\Catalog\Controller\Product\Compare +class Index extends \Magento\Catalog\Controller\Product\Compare implements HttpGetActionInterface { /** * @var \Magento\Framework\Url\DecoderInterface diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php index 2acbe5ce4d5..eac0ddf94af 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php @@ -6,9 +6,10 @@ */ namespace Magento\Catalog\Controller\Product\Compare; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; -class Remove extends \Magento\Catalog\Controller\Product\Compare +class Remove extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface { /** * Remove item from compare list diff --git a/app/code/Magento/Catalog/Controller/Product/View.php b/app/code/Magento/Catalog/Controller/Product/View.php index ed437361fdd..024123e1515 100644 --- a/app/code/Magento/Catalog/Controller/Product/View.php +++ b/app/code/Magento/Catalog/Controller/Product/View.php @@ -6,10 +6,16 @@ */ namespace Magento\Catalog\Controller\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; +use Magento\Catalog\Controller\Product as ProductAction; -class View extends \Magento\Catalog\Controller\Product +/** + * View a product on storefront. Needs to be accessible by POST because of the store switching. + */ +class View extends ProductAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Catalog\Helper\Product\View diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index cf79ff01d31..29fb0d282a8 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -72,6 +72,11 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements const CACHE_TAG = 'cat_c'; + /** + * Category Store Id + */ + const STORE_ID = 'store_id'; + /**#@-*/ protected $_eventPrefix = 'catalog_category'; @@ -300,7 +305,7 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ protected function getCustomAttributesCodes() { @@ -312,6 +317,8 @@ protected function getCustomAttributesCodes() } /** + * Returns model resource + * * @throws \Magento\Framework\Exception\LocalizedException * @return \Magento\Catalog\Model\ResourceModel\Category * @deprecated because resource models should be used directly @@ -562,12 +569,12 @@ public function getStoreIds() * * If store id is underfined for category return current active store id * - * @return integer + * @return int */ public function getStoreId() { - if ($this->hasData('store_id')) { - return (int)$this->_getData('store_id'); + if ($this->hasData(self::STORE_ID)) { + return (int)$this->_getData(self::STORE_ID); } return (int)$this->_storeManager->getStore()->getId(); } @@ -583,7 +590,7 @@ public function setStoreId($storeId) if (!is_numeric($storeId)) { $storeId = $this->_storeManager->getStore($storeId)->getId(); } - $this->setData('store_id', $storeId); + $this->setData(self::STORE_ID, $storeId); $this->getResource()->setStoreId($storeId); return $this; } @@ -648,6 +655,8 @@ public function formatUrlKey($str) } /** + * Returns image url + * * @param string $attributeCode * @return bool|string * @throws \Magento\Framework\Exception\LocalizedException @@ -708,7 +717,7 @@ public function getParentId() return $parentId; } $parentIds = $this->getParentIds(); - return intval(array_pop($parentIds)); + return (int) array_pop($parentIds); } /** @@ -796,6 +805,7 @@ public function getChildren($recursive = false, $isActive = true, $sortByPositio /** * Retrieve Stores where isset category Path + * * Return comma separated string * * @return string @@ -826,6 +836,7 @@ public function checkId($id) /** * Get array categories ids which are part of category path + * * Result array contain id of current category because it is part of the path * * @return array @@ -1029,7 +1040,8 @@ public function getAvailableSortBy() /** * Retrieve Available Product Listing Sort By - * code as key, value - name + * + * Code as key, value - name * * @return array */ @@ -1150,6 +1162,8 @@ public function getIdentities() } /** + * Returns path + * * @codeCoverageIgnoreStart * @return string|null */ @@ -1159,6 +1173,8 @@ public function getPath() } /** + * Returns position + * * @return int|null */ public function getPosition() @@ -1167,6 +1183,8 @@ public function getPosition() } /** + * Returns children count + * * @return int */ public function getChildrenCount() @@ -1175,6 +1193,8 @@ public function getChildrenCount() } /** + * Returns created at + * * @return string|null */ public function getCreatedAt() @@ -1183,6 +1203,8 @@ public function getCreatedAt() } /** + * Returns updated at + * * @return string|null */ public function getUpdatedAt() @@ -1191,6 +1213,8 @@ public function getUpdatedAt() } /** + * Returns is active + * * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ @@ -1200,6 +1224,8 @@ public function getIsActive() } /** + * Returns category id + * * @return int|null */ public function getCategoryId() @@ -1208,6 +1234,8 @@ public function getCategoryId() } /** + * Returns display mode + * * @return string|null */ public function getDisplayMode() @@ -1216,6 +1244,8 @@ public function getDisplayMode() } /** + * Returns is include in menu + * * @return bool|null */ public function getIncludeInMenu() @@ -1224,6 +1254,8 @@ public function getIncludeInMenu() } /** + * Returns url key + * * @return string|null */ public function getUrlKey() @@ -1232,6 +1264,8 @@ public function getUrlKey() } /** + * Returns children data + * * @return \Magento\Catalog\Api\Data\CategoryTreeInterface[]|null */ public function getChildrenData() @@ -1347,6 +1381,8 @@ public function setLevel($level) } /** + * Set updated at + * * @param string $updatedAt * @return $this */ @@ -1356,6 +1392,8 @@ public function setUpdatedAt($updatedAt) } /** + * Set created at + * * @param string $createdAt * @return $this */ @@ -1365,6 +1403,8 @@ public function setCreatedAt($createdAt) } /** + * Set path + * * @param string $path * @return $this */ @@ -1374,6 +1414,8 @@ public function setPath($path) } /** + * Set available sort by + * * @param string[]|string $availableSortBy * @return $this */ @@ -1383,6 +1425,8 @@ public function setAvailableSortBy($availableSortBy) } /** + * Set include in menu + * * @param bool $includeInMenu * @return $this */ @@ -1403,6 +1447,8 @@ public function setProductCount($productCount) } /** + * Set children data + * * @param \Magento\Catalog\Api\Data\CategoryTreeInterface[] $childrenData * @return $this */ @@ -1412,7 +1458,7 @@ public function setChildrenData(array $childrenData = null) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Catalog\Api\Data\CategoryExtensionInterface|null */ @@ -1422,7 +1468,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/Catalog/Model/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php index 1c206086706..dcc70cbcd2a 100644 --- a/app/code/Magento/Catalog/Model/ImageExtractor.php +++ b/app/code/Magento/Catalog/Model/ImageExtractor.php @@ -9,6 +9,9 @@ use Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter; use Magento\Framework\View\Xsd\Media\TypeDataExtractorInterface; +/** + * Image extractor from xml configuration + */ class ImageExtractor implements TypeDataExtractorInterface { /** @@ -36,7 +39,7 @@ public function process(\DOMElement $mediaNode, $mediaParentTag) if ($attributeTagName === 'background') { $nodeValue = $this->processImageBackground($attribute->nodeValue); } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') { - $nodeValue = intval($attribute->nodeValue); + $nodeValue = (int) $attribute->nodeValue; } else { $nodeValue = $attribute->nodeValue; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php index 8182e6f07fa..6762602aeca 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php @@ -8,7 +8,12 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat\Action; use Magento\Framework\App\ResourceConnection; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Store\Model\Store; +/** + * Flat item eraser. Used to clear items from the catalog flat table. + */ class Eraser { /** @@ -50,12 +55,7 @@ public function __construct( */ public function removeDeletedProducts(array &$ids, $storeId) { - $select = $this->connection->select()->from( - $this->productIndexerHelper->getTable('catalog_product_entity') - )->where( - 'entity_id IN(?)', - $ids - ); + $select = $this->getSelectForProducts($ids); $result = $this->connection->query($select); $existentProducts = []; @@ -69,6 +69,61 @@ public function removeDeletedProducts(array &$ids, $storeId) $this->deleteProductsFromStore($productsToDelete, $storeId); } + /** + * Remove products with "Disabled" status from the flat table(s). + * + * @param array $ids + * @param int $storeId + * @return void + */ + public function removeDisabledProducts(array &$ids, $storeId) + { + /* @var $statusAttribute \Magento\Eav\Model\Entity\Attribute */ + $statusAttribute = $this->productIndexerHelper->getAttribute('status'); + + $select = $this->getSelectForProducts($ids); + $select->joinLeft( + ['status_global_attr' => $statusAttribute->getBackendTable()], + ' status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID, + [] + ); + $select->joinLeft( + ['status_attr' => $statusAttribute->getBackendTable()], + ' status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() + . ' AND status_attr.store_id = ' . $storeId, + [] + ); + $select->where('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_DISABLED); + + $result = $this->connection->query($select); + + $disabledProducts = []; + foreach ($result->fetchAll() as $product) { + $disabledProducts[] = $product['entity_id']; + } + + if (!empty($disabledProducts)) { + $ids = array_diff($ids, $disabledProducts); + $this->deleteProductsFromStore($disabledProducts, $storeId); + } + } + + /** + * Get Select object for existed products. + * + * @param array $ids + * @return \Magento\Framework\DB\Select + */ + private function getSelectForProducts(array $ids) + { + $productTable = $this->productIndexerHelper->getTable('catalog_product_entity'); + $select = $this->connection->select()->from($productTable) + ->columns('entity_id') + ->where('entity_id IN(?)', $ids); + return $select; + } + /** * Delete products from flat table(s) * diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php index 3a161129928..466ba746fa1 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php @@ -56,39 +56,36 @@ public function __construct( * @return \Magento\Catalog\Model\Indexer\Product\Flat * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function write($storeId, $productId, $valueFieldSuffix = '') { $flatTable = $this->_productIndexerHelper->getFlatTableName($storeId); + $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity'); $attributes = $this->_productIndexerHelper->getAttributes(); $eavAttributes = $this->_productIndexerHelper->getTablesStructure($attributes); $updateData = []; $describe = $this->_connection->describeTable($flatTable); + $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); foreach ($eavAttributes as $tableName => $tableColumns) { $columnsChunks = array_chunk($tableColumns, self::ATTRIBUTES_CHUNK_SIZE, true); foreach ($columnsChunks as $columns) { $select = $this->_connection->select(); - $selectValue = $this->_connection->select(); - $keyColumns = [ - 'entity_id' => 'e.entity_id', - 'attribute_id' => 't.attribute_id', - 'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'), - ]; - - if ($tableName != $this->_productIndexerHelper->getTable('catalog_product_entity')) { + + if ($tableName != $entityTableName) { $valueColumns = []; $ids = []; $select->from( - ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')], - $keyColumns - ); - - $selectValue->from( - ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')], - $keyColumns + ['e' => $entityTableName], + [ + 'entity_id' => 'e.entity_id', + 'attribute_id' => 't.attribute_id', + 'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'), + ] ); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ @@ -97,8 +94,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '') $ids[$attribute->getId()] = $columnName; } } - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); - $select->joinLeft( + $select->joinInner( ['t' => $tableName], sprintf('e.%s = t.%s ', $linkField, $linkField) . $this->_connection->quoteInto( ' AND t.attribute_id IN (?)', @@ -116,8 +112,6 @@ public function write($storeId, $productId, $valueFieldSuffix = '') [] )->where( 'e.entity_id = ' . $productId - )->where( - 't.attribute_id IS NOT NULL' ); $cursor = $this->_connection->query($select); while ($row = $cursor->fetch(\Zend_Db::FETCH_ASSOC)) { @@ -157,7 +151,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '') $columnNames[] = 'attribute_set_id'; $columnNames[] = 'type_id'; $select->from( - ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')], + ['e' => $entityTableName], $columnNames )->where( 'e.entity_id = ' . $productId @@ -175,7 +169,9 @@ public function write($storeId, $productId, $valueFieldSuffix = '') if (!empty($updateData)) { $updateData += ['entity_id' => $productId]; - $updateData += ['row_id' => $productId]; + if ($linkField !== $metadata->getIdentifierField()) { + $updateData += [$linkField => $productId]; + } $updateFields = []; foreach ($updateData as $key => $value) { $updateFields[$key] = $key; @@ -187,6 +183,8 @@ public function write($storeId, $productId, $valueFieldSuffix = '') } /** + * Get MetadataPool instance + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php index 709f27d031e..d9bfc96b781 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php @@ -5,16 +5,20 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Flat\Action; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder; use Magento\Catalog\Model\Indexer\Product\Flat\TableBuilder; +use Magento\Framework\EntityManager\MetadataPool; /** - * Class Row reindex action + * Class Row reindex action. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction { /** - * @var \Magento\Catalog\Model\Indexer\Product\Flat\Action\Indexer + * @var Indexer */ protected $flatItemWriter; @@ -23,6 +27,11 @@ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction */ protected $flatItemEraser; + /** + * @var MetadataPool + */ + private $metadataPool; + /** * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -32,6 +41,7 @@ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction * @param FlatTableBuilder $flatTableBuilder * @param Indexer $flatItemWriter * @param Eraser $flatItemEraser + * @param MetadataPool|null $metadataPool */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, @@ -41,7 +51,8 @@ public function __construct( TableBuilder $tableBuilder, FlatTableBuilder $flatTableBuilder, Indexer $flatItemWriter, - Eraser $flatItemEraser + Eraser $flatItemEraser, + MetadataPool $metadataPool = null ) { parent::__construct( $resource, @@ -53,6 +64,8 @@ public function __construct( ); $this->flatItemWriter = $flatItemWriter; $this->flatItemEraser = $flatItemEraser; + $this->metadataPool = $metadataPool ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(MetadataPool::class); } /** @@ -61,7 +74,6 @@ public function __construct( * @param int|null $id * @return \Magento\Catalog\Model\Indexer\Product\Flat\Action\Row * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Zend_Db_Statement_Exception */ public function execute($id = null) { @@ -71,50 +83,48 @@ public function execute($id = null) ); } $ids = [$id]; - foreach ($this->_storeManager->getStores() as $store) { + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + $stores = $this->_storeManager->getStores(); + foreach ($stores as $store) { $tableExists = $this->_isFlatTableExists($store->getId()); if ($tableExists) { $this->flatItemEraser->removeDeletedProducts($ids, $store->getId()); + $this->flatItemEraser->removeDisabledProducts($ids, $store->getId()); } /* @var $status \Magento\Eav\Model\Entity\Attribute */ - $status = $this->_productIndexerHelper->getAttribute('status'); + $status = $this->_productIndexerHelper->getAttribute(ProductInterface::STATUS); $statusTable = $status->getBackend()->getTable(); $statusConditions = [ 'store_id IN(0,' . (int)$store->getId() . ')', 'attribute_id = ' . (int)$status->getId(), - 'entity_id = ' . (int)$id + $linkField . ' = ' . (int)$id, ]; $select = $this->_connection->select(); - $select->from( - $statusTable, - ['value'] - )->where( - implode(' AND ', $statusConditions) - )->order( - 'store_id DESC' - ); + $select->from($statusTable, ['value']) + ->where(implode(' AND ', $statusConditions)) + ->order('store_id DESC') + ->limit(1); $result = $this->_connection->query($select); - $status = $result->fetch(1); + $status = $result->fetchColumn(0); - if ($status['value'] == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) { - if (isset($ids[0])) { - if (!$tableExists) { - $this->_flatTableBuilder->build( - $store->getId(), - [$ids[0]], - $this->_valueFieldSuffix, - $this->_tableDropSuffix, - false - ); - } - $this->flatItemWriter->write($store->getId(), $ids[0], $this->_valueFieldSuffix); + if ($status == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) { + if (!$tableExists) { + $this->_flatTableBuilder->build( + $store->getId(), + $ids, + $this->_valueFieldSuffix, + $this->_tableDropSuffix, + false + ); } - } - if ($status['value'] == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED) { + $this->flatItemWriter->write($store->getId(), $id, $this->_valueFieldSuffix); + } else { $this->flatItemEraser->deleteProductsFromStore($id, $store->getId()); } } + return $this; } } diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index b29eee9a47f..753fb0b5e18 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -498,8 +498,9 @@ protected function _getResource() } /** - * Get a list of custom attribute codes that belongs to product attribute set. If attribute set not specified for - * product will return all product attribute codes + * Get a list of custom attribute codes that belongs to product attribute set. + * + * If attribute set not specified for product will return all product attribute codes * * @return string[] */ @@ -526,9 +527,9 @@ protected function getCustomAttributesCodes() public function getStoreId() { if ($this->hasData(self::STORE_ID)) { - return $this->getData(self::STORE_ID); + return (int)$this->getData(self::STORE_ID); } - return $this->_storeManager->getStore()->getId(); + return (int)$this->_storeManager->getStore()->getId(); } /** @@ -584,8 +585,9 @@ public function getPrice() } /** - * @codeCoverageIgnoreStart * Get visibility status + * + * @codeCoverageIgnoreStart * @see \Magento\Catalog\Model\Product\Visibility * * @return int @@ -662,6 +664,7 @@ public function getStatus() /** * Retrieve type instance of the product. + * * Type instance implements product type depended logic and is a singleton shared by all products of the same type. * * @return \Magento\Catalog\Model\Product\Type\AbstractType @@ -822,9 +825,10 @@ public function getStoreIds() /** * Retrieve product attributes - * if $groupId is null - retrieve all product attributes * - * @param int $groupId Retrieve attributes of the specified group + * If $groupId is null - retrieve all product attributes + * + * @param int $groupId Retrieve attributes of the specified group * @param bool $skipSuper Not used * @return \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[] * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -916,6 +920,7 @@ public function beforeSave() /** * Check/set if options can be affected when saving product + * * If value specified, it will be set. * * @param bool $value @@ -976,7 +981,7 @@ public function setQty($qty) */ public function getQty() { - return $this->getData('qty'); + return (float)$this->getData('qty'); } /** @@ -1036,6 +1041,7 @@ public function reindex() /** * Clear cache related with product and protect delete from not admin + * * Register indexing event before delete product * * @return \Magento\Catalog\Model\Product @@ -1545,11 +1551,11 @@ public function hasGalleryAttribute() /** * Add image to media gallery * - * @param string $file file path of image in file system - * @param string|array $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 + * @param string $file file path of image in file system + * @param string|array $mediaAttribute code of attribute with type 'media_image', + * leave blank if image should be only in gallery + * @param bool $move if true, it will move source file + * @param bool $exclude mark image as disabled in product page view * @return \Magento\Catalog\Model\Product */ public function addImageToMediaGallery($file, $mediaAttribute = null, $move = false, $exclude = true) @@ -1711,6 +1717,7 @@ public function getIsSalable() /** * Check is a virtual product + * * Data helper wrapper * * @return bool @@ -1803,9 +1810,9 @@ public function formatUrlKey($str) /** * Save current attribute with code $code and assign new value * - * @param string $code Attribute code - * @param mixed $value New attribute value - * @param int $store Store ID + * @param string $code Attribute code + * @param mixed $value New attribute value + * @param int $store Store ID * @return void */ public function addAttributeUpdate($code, $value, $store) @@ -1875,6 +1882,7 @@ public function getRequestPath() /** * Custom function for other modules + * * @return string */ public function getGiftMessageAvailable() @@ -1993,6 +2001,8 @@ public function getOptions() } /** + * Set product options + * * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface[] $options * @return $this */ @@ -2016,10 +2026,10 @@ public function getIsVirtual() /** * Add custom option information to product * - * @param string $code Option code - * @param mixed $value Value of the option - * @param int|Product $product Product ID - * @return $this + * @param string $code Option code + * @param mixed $value Value of the option + * @param int|Product $product Product ID + * @return $this */ public function addCustomOption($code, $value, $product = null) { @@ -2213,6 +2223,7 @@ public function getPreconfiguredValues() /** * Prepare product custom options. + * * To be sure that all product custom options does not has ID and has product instance * * @return \Magento\Catalog\Model\Product @@ -2547,7 +2558,7 @@ public function setTypeId($typeId) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Catalog\Api\Data\ProductExtensionInterface */ @@ -2557,7 +2568,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes * @return $this @@ -2570,6 +2581,8 @@ public function setExtensionAttributes(\Magento\Catalog\Api\Data\ProductExtensio //@codeCoverageIgnoreEnd /** + * Convert array to media gallery interface + * * @param array $mediaGallery * @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[] */ @@ -2587,6 +2600,8 @@ protected function convertToMediaGalleryInterface(array $mediaGallery) } /** + * Returns media gallery entries + * * @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]|null */ public function getMediaGalleryEntries() @@ -2601,6 +2616,8 @@ public function getMediaGalleryEntries() } /** + * Set media gallery entries + * * @param ProductAttributeMediaGalleryEntryInterface[] $mediaGalleryEntries * @return $this */ @@ -2643,6 +2660,8 @@ public function setId($value) } /** + * Returns link repository instance + * * @return ProductLinkRepositoryInterface */ private function getLinkRepository() @@ -2655,6 +2674,8 @@ private function getLinkRepository() } /** + * Returns media gallery processor instance + * * @return Product\Gallery\Processor */ private function getMediaGalleryProcessor() diff --git a/app/code/Magento/Catalog/Model/Product/Compare/Item.php b/app/code/Magento/Catalog/Model/Product/Compare/Item.php index 3b7e47a46a0..fd07380bebd 100644 --- a/app/code/Magento/Catalog/Model/Product/Compare/Item.php +++ b/app/code/Magento/Catalog/Model/Product/Compare/Item.php @@ -158,8 +158,8 @@ public function addProductData($product) { if ($product instanceof Product) { $this->setProductId($product->getId()); - } elseif (intval($product)) { - $this->setProductId(intval($product)); + } elseif ((int) $product) { + $this->setProductId((int) $product); } return $this; diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php index 8ad8dcb4812..189135776b6 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php @@ -16,7 +16,8 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler { /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ protected function processDeletedImages($product, array &$images) @@ -31,7 +32,7 @@ protected function processDeletedImages($product, array &$images) foreach ($images as &$image) { if (!empty($image['removed'])) { - if (!empty($image['value_id']) && !isset($picturesInOtherStores[$image['file']])) { + if (!empty($image['value_id'])) { if (preg_match('/\.\.(\\\|\/)/', $image['file'])) { continue; } @@ -52,7 +53,8 @@ protected function processDeletedImages($product, array &$images) } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ protected function processNewImage($product, array &$image) @@ -79,6 +81,8 @@ protected function processNewImage($product, array &$image) } /** + * Retrieve store ids from product. + * * @param \Magento\Catalog\Model\Product $product * @return array * @since 101.0.0 @@ -97,6 +101,8 @@ protected function extractStoreIds($product) } /** + * Remove deleted images. + * * @param array $files * @return null * @since 101.0.0 diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php index 7517459da65..b19906ecd6c 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php @@ -102,11 +102,11 @@ public function validateUserValue($values) $this->setUserValue( [ 'date' => isset($value['date']) ? $value['date'] : '', - 'year' => isset($value['year']) ? intval($value['year']) : 0, - 'month' => isset($value['month']) ? intval($value['month']) : 0, - 'day' => isset($value['day']) ? intval($value['day']) : 0, - 'hour' => isset($value['hour']) ? intval($value['hour']) : 0, - 'minute' => isset($value['minute']) ? intval($value['minute']) : 0, + 'year' => isset($value['year']) ? (int) $value['year'] : 0, + 'month' => isset($value['month']) ? (int) $value['month'] : 0, + 'day' => isset($value['day']) ? (int) $value['day'] : 0, + 'hour' => isset($value['hour']) ? (int) $value['hour'] : 0, + 'minute' => isset($value['minute']) ? (int) $value['minute'] : 0, 'day_part' => isset($value['day_part']) ? $value['day_part'] : '', 'date_internal' => isset($value['date_internal']) ? $value['date_internal'] : '', ] diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php index ee508e30cc9..99d5016f5cd 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php @@ -9,6 +9,9 @@ use Magento\Catalog\Model\Product\Option; use Zend_Validate_Exception; +/** + * Product option default validator + */ class DefaultValidator extends \Magento\Framework\Validator\AbstractValidator { /** @@ -168,6 +171,6 @@ protected function isInRange($value, array $range) */ protected function isNegative($value) { - return intval($value) < 0; + return (int) $value < 0; } } diff --git a/app/code/Magento/Catalog/Model/Product/PriceModifier.php b/app/code/Magento/Catalog/Model/Product/PriceModifier.php index 48d53b46145..c4d5bdfedcd 100644 --- a/app/code/Magento/Catalog/Model/Product/PriceModifier.php +++ b/app/code/Magento/Catalog/Model/Product/PriceModifier.php @@ -9,6 +9,9 @@ use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; +/** + * Product form price modifier + */ class PriceModifier { /** @@ -26,6 +29,8 @@ public function __construct( } /** + * Remove tier price + * * @param \Magento\Catalog\Model\Product $product * @param int|string $customerGroupId * @param int $qty @@ -46,11 +51,11 @@ public function removeTierPrice(\Magento\Catalog\Model\Product $product, $custom foreach ($prices as $key => $tierPrice) { if ($customerGroupId == 'all' && $tierPrice['price_qty'] == $qty - && $tierPrice['all_groups'] == 1 && intval($tierPrice['website_id']) === intval($websiteId) + && $tierPrice['all_groups'] == 1 && (int) $tierPrice['website_id'] === (int) $websiteId ) { unset($prices[$key]); } elseif ($tierPrice['price_qty'] == $qty && $tierPrice['cust_group'] == $customerGroupId - && intval($tierPrice['website_id']) === intval($websiteId) + && (int) $tierPrice['website_id'] === (int) $websiteId ) { unset($prices[$key]); } diff --git a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php index 822959bfc85..f2da1e77027 100644 --- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php +++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php @@ -15,6 +15,8 @@ use Magento\Framework\Exception\TemporaryStateExceptionInterface; /** + * Product tier price management + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TierPriceManagement implements \Magento\Catalog\Api\ProductTierPriceManagementInterface @@ -82,7 +84,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -148,7 +150,7 @@ public function add($sku, $customerGroupId, $price, $qty) } /** - * {@inheritdoc} + * @inheritdoc */ public function remove($sku, $customerGroupId, $qty) { @@ -163,7 +165,7 @@ public function remove($sku, $customerGroupId, $qty) } /** - * {@inheritdoc} + * @inheritdoc */ public function getList($sku, $customerGroupId) { @@ -181,7 +183,7 @@ public function getList($sku, $customerGroupId) $prices = []; foreach ($product->getData('tier_price') as $price) { - if ((is_numeric($customerGroupId) && intval($price['cust_group']) === intval($customerGroupId)) + if ((is_numeric($customerGroupId) && (int) $price['cust_group'] === (int) $customerGroupId) || ($customerGroupId === 'all' && $price['all_groups']) ) { /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */ diff --git a/app/code/Magento/Catalog/Model/Product/Type.php b/app/code/Magento/Catalog/Model/Product/Type.php index 7be199884be..4c973be20de 100644 --- a/app/code/Magento/Catalog/Model/Product/Type.php +++ b/app/code/Magento/Catalog/Model/Product/Type.php @@ -285,7 +285,7 @@ public function getTypesByPriority() $types = $this->getTypes(); foreach ($types as $typeId => $typeInfo) { - $priority = isset($typeInfo['index_priority']) ? abs(intval($typeInfo['index_priority'])) : 0; + $priority = isset($typeInfo['index_priority']) ? abs((int) $typeInfo['index_priority']) : 0; if (!empty($typeInfo['composite'])) { $compositePriority[$typeId] = $priority; } else { @@ -307,7 +307,7 @@ public function getTypesByPriority() } /** - * {@inheritdoc} + * @inheritdoc */ public function toOptionArray() { diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php index c291dc33fed..f3ac9f55d1a 100644 --- a/app/code/Magento/Catalog/Model/Product/Url.php +++ b/app/code/Magento/Catalog/Model/Product/Url.php @@ -7,6 +7,7 @@ use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\Framework\App\Config\ScopeConfigInterface; /** * Product Url model @@ -45,6 +46,11 @@ class Url extends \Magento\Framework\DataObject */ protected $urlFinder; + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + /** * @param \Magento\Framework\UrlFactory $urlFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -52,6 +58,7 @@ class Url extends \Magento\Framework\DataObject * @param \Magento\Framework\Session\SidResolverInterface $sidResolver * @param UrlFinderInterface $urlFinder * @param array $data + * @param ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Framework\UrlFactory $urlFactory, @@ -59,7 +66,8 @@ public function __construct( \Magento\Framework\Filter\FilterManager $filter, \Magento\Framework\Session\SidResolverInterface $sidResolver, UrlFinderInterface $urlFinder, - array $data = [] + array $data = [], + ScopeConfigInterface $scopeConfig = null ) { parent::__construct($data); $this->urlFactory = $urlFactory; @@ -67,16 +75,8 @@ public function __construct( $this->filter = $filter; $this->sidResolver = $sidResolver; $this->urlFinder = $urlFinder; - } - - /** - * Retrieve URL Instance - * - * @return \Magento\Framework\UrlInterface - */ - private function getUrlInstance() - { - return $this->urlFactory->create(); + $this->scopeConfig = $scopeConfig ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -157,10 +157,19 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = []) UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE, UrlRewrite::STORE_ID => $storeId, ]; + $useCategories = $this->scopeConfig->getValue( + \Magento\Catalog\Helper\Product::XML_PATH_PRODUCT_URL_USE_CATEGORY, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + if ($categoryId) { $filterData[UrlRewrite::METADATA]['category_id'] = $categoryId; + } elseif (!$useCategories) { + $filterData[UrlRewrite::METADATA]['category_id'] = ''; } + $rewrite = $this->urlFinder->findOneByData($filterData); + if ($rewrite) { $requestPath = $rewrite->getRequestPath(); $product->setRequestPath($requestPath); @@ -194,6 +203,7 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = []) $routeParams['_query'] = []; } - return $this->getUrlInstance()->setScope($storeId)->getUrl($routePath, $routeParams); + $url = $this->urlFactory->create()->setScope($storeId); + return $url->getUrl($routePath, $routeParams); } } diff --git a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php index bc212adae2c..13f0dbf79a1 100644 --- a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php +++ b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php @@ -9,6 +9,9 @@ use Magento\Catalog\Model\ProductLink\Converter\ConverterPool; use Magento\Framework\Exception\NoSuchEntityException; +/** + * Provides a collection of linked product items (crosssells, related, upsells, ...) + */ class CollectionProvider { /** @@ -47,22 +50,20 @@ public function getCollection(\Magento\Catalog\Model\Product $product, $type) $products = $this->providers[$type]->getLinkedProducts($product); $converter = $this->converterPool->getConverter($type); - $output = []; $sorterItems = []; foreach ($products as $item) { - $output[$item->getId()] = $converter->convert($item); + $itemId = $item->getId(); + $sorterItems[$itemId] = $converter->convert($item); + $sorterItems[$itemId]['position'] = $sorterItems[$itemId]['position'] ?? 0; } - foreach ($output as $item) { - $itemPosition = $item['position']; - if (!isset($sorterItems[$itemPosition])) { - $sorterItems[$itemPosition] = $item; - } else { - $newPosition = $itemPosition + 1; - $sorterItems[$newPosition] = $item; - } - } - ksort($sorterItems); + usort($sorterItems, function ($itemA, $itemB) { + $posA = intval($itemA['position']); + $posB = intval($itemB['position']); + + return $posA <=> $posB; + }); + return $sorterItems; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 9de0e8a8490..5b420c33b31 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -16,6 +16,8 @@ use Magento\Framework\EntityManager\EntityManager; /** + * Resource model for category entity + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Category extends AbstractResource @@ -249,7 +251,8 @@ public function deleteChildren(\Magento\Framework\DataObject $object) /** * Process category data before saving - * prepare path and increment children count for parent categories + * + * Prepare path and increment children count for parent categories * * @param \Magento\Framework\DataObject $object * @return $this @@ -298,7 +301,8 @@ protected function _beforeSave(\Magento\Framework\DataObject $object) /** * Process category data after save category object - * save related products ids and update path value + * + * Save related products ids and update path value * * @param \Magento\Framework\DataObject $object * @return $this @@ -664,7 +668,7 @@ public function getProductCount($category) $bind = ['category_id' => (int)$category->getId()]; $counts = $this->getConnection()->fetchOne($select, $bind); - return intval($counts); + return (int) $counts; } /** @@ -862,6 +866,7 @@ public function isInRootCategoryList($category) /** * Check category is forbidden to delete. + * * If category is root and assigned to store group return false * * @param integer $categoryId @@ -918,7 +923,7 @@ public function changeParent( $childrenCount = $this->getChildrenCount($category->getId()) + 1; $table = $this->getEntityTable(); $connection = $this->getConnection(); - $levelFiled = $connection->quoteIdentifier('level'); + $levelField = $connection->quoteIdentifier('level'); $pathField = $connection->quoteIdentifier('path'); /** @@ -958,7 +963,7 @@ public function changeParent( $newPath . '/' ) . ')' ), - 'level' => new \Zend_Db_Expr($levelFiled . ' + ' . $levelDisposition) + 'level' => new \Zend_Db_Expr($levelField . ' + ' . $levelDisposition) ], [$pathField . ' LIKE ?' => $category->getPath() . '/%'] ); @@ -982,6 +987,7 @@ public function changeParent( /** * Process positions of old parent category children and new parent category children. + * * Get position for moved category * * @param \Magento\Catalog\Model\Category $category @@ -1062,7 +1068,7 @@ public function load($object, $entityId, $attributes = []) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete($object) { @@ -1088,6 +1094,8 @@ public function save(\Magento\Framework\Model\AbstractModel $object) } /** + * Returns EntityManager object + * * @return EntityManager */ private function getEntityManager() @@ -1100,6 +1108,8 @@ private function getEntityManager() } /** + * Returns AggregateCount object + * * @return Category\AggregateCount */ private function getAggregateCount() diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php index 01e4b072b03..9db2c8248ce 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php @@ -173,7 +173,7 @@ public function getMainTable() public function getMainStoreTable($storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID) { if (is_string($storeId)) { - $storeId = intval($storeId); + $storeId = (int) $storeId; } if ($storeId) { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index 8f8e9f6bfed..707ebbb2964 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -167,6 +167,8 @@ public function __construct( } /** + * Init model + * * @return void */ protected function _construct() @@ -362,6 +364,7 @@ public function getStoreId() /** * Retrieve apply to products array + * * Return empty array if applied to all products * * @return string[] @@ -478,7 +481,7 @@ protected function _isOriginalIndexable() $backendType = $this->getOrigData('backend_type'); $frontendInput = $this->getOrigData('frontend_input'); - if ($backendType == 'int' && $frontendInput == 'select') { + if ($backendType == 'int' && ($frontendInput == 'select' || $frontendInput == 'boolean')) { return true; } elseif ($backendType == 'varchar' && $frontendInput == 'multiselect') { return true; @@ -507,8 +510,8 @@ public function getIndexType() } /** + * @inheritdoc * @codeCoverageIgnoreStart - * {@inheritdoc} */ public function getIsWysiwygEnabled() { @@ -516,7 +519,7 @@ public function getIsWysiwygEnabled() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsHtmlAllowedOnFront() { @@ -524,7 +527,7 @@ public function getIsHtmlAllowedOnFront() } /** - * {@inheritdoc} + * @inheritdoc */ public function getUsedForSortBy() { @@ -532,7 +535,7 @@ public function getUsedForSortBy() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsFilterable() { @@ -540,7 +543,7 @@ public function getIsFilterable() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsFilterableInSearch() { @@ -548,7 +551,7 @@ public function getIsFilterableInSearch() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsUsedInGrid() { @@ -556,7 +559,7 @@ public function getIsUsedInGrid() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsVisibleInGrid() { @@ -564,7 +567,7 @@ public function getIsVisibleInGrid() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsFilterableInGrid() { @@ -572,7 +575,7 @@ public function getIsFilterableInGrid() } /** - * {@inheritdoc} + * @inheritdoc */ public function getPosition() { @@ -580,7 +583,7 @@ public function getPosition() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsSearchable() { @@ -588,7 +591,7 @@ public function getIsSearchable() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsVisibleInAdvancedSearch() { @@ -596,7 +599,7 @@ public function getIsVisibleInAdvancedSearch() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsComparable() { @@ -604,7 +607,7 @@ public function getIsComparable() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsUsedForPromoRules() { @@ -612,7 +615,7 @@ public function getIsUsedForPromoRules() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsVisibleOnFront() { @@ -620,7 +623,7 @@ public function getIsVisibleOnFront() } /** - * {@inheritdoc} + * @inheritdoc */ public function getUsedInProductListing() { @@ -628,7 +631,7 @@ public function getUsedInProductListing() } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsVisible() { @@ -638,7 +641,7 @@ public function getIsVisible() //@codeCoverageIgnoreEnd /** - * {@inheritdoc} + * @inheritdoc */ public function getScope() { @@ -720,7 +723,7 @@ public function setPosition($position) /** * Set apply to value for the element * - * @param string []|string + * @param string[]|string $applyTo * @return $this */ public function setApplyTo($applyTo) @@ -829,7 +832,7 @@ public function setScope($scope) } /** - * {@inheritdoc} + * @inheritdoc */ public function afterDelete() { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index fdd98442150..0d62d120f80 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1423,8 +1423,13 @@ protected function _addUrlRewrite() ['cu' => $this->getTable('catalog_url_rewrite_product_category')], 'u.url_rewrite_id=cu.url_rewrite_id' )->where('cu.category_id IN (?)', $this->_urlRewriteCategory); + } else { + $select->joinLeft( + ['cu' => $this->getTable('catalog_url_rewrite_product_category')], + 'u.url_rewrite_id=cu.url_rewrite_id' + )->where('cu.url_rewrite_id IS NULL'); } - + // more priority is data with category id $urlRewrites = []; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php index 5b68730209b..3a61e7e3644 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php @@ -84,7 +84,7 @@ protected function _getIndexableAttributes($multiSelect) if ($multiSelect == true) { $select->where('ea.backend_type = ?', 'varchar')->where('ea.frontend_input = ?', 'multiselect'); } else { - $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input = ?', 'select'); + $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input IN( ? )', ['select', 'boolean']); } return $this->getConnection()->fetchCol($select); @@ -330,6 +330,8 @@ public function getIdxTable($table = null) } /** + * Save data from select + * * @param \Magento\Framework\DB\Select $select * @param array $options * @return void diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index ce0a9b6e461..318c9bd132c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -85,6 +85,7 @@ protected function _construct() /** * Proceed operations after object is saved + * * Save options store data * * @param AbstractModel $object @@ -160,19 +161,22 @@ protected function _saveValuePrices(AbstractModel $object) && isset($objectPrice) && $object->getStoreId() != Store::DEFAULT_STORE_ID ) { - $baseCurrency = $this->_config->getValue( + $website = $this->_storeManager->getStore($object->getStoreId())->getWebsite(); + + $websiteBaseCurrency = $this->_config->getValue( Currency::XML_PATH_CURRENCY_BASE, - 'default' + ScopeInterface::SCOPE_WEBSITE, + $website ); - $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds(); + $storeIds = $website->getStoreIds(); if (is_array($storeIds)) { foreach ($storeIds as $storeId) { if ($priceType == 'fixed') { $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); /** @var $currencyModel Currency */ $currencyModel = $this->_currencyFactory->create(); - $currencyModel->load($baseCurrency); + $currencyModel->load($websiteBaseCurrency); $rate = $currencyModel->getRate($storeCurrency); if (!$rate) { $rate = 1; diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml index a2ad155672a..784eff12a6c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml @@ -13,5 +13,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 545b685f4d2..51199300206 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -23,6 +23,7 @@ + @@ -117,7 +118,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml index dea1b2a5af7..54b289a89c7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml new file mode 100644 index 00000000000..060720ab007 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml @@ -0,0 +1,129 @@ + + + + + + + + + + <description value="Product image should be deleted from all scopes"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94265"/> + <group value="Catalog"/> + </annotations> + <before> + <!--Create 2 websites (with stores, store views)--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="category"/> + <createData entity="_defaultProduct" stepKey="product"> + <requiredEntity createDataKey="category"/> + </createData> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="FirstWebSite"/> + <argument name="websiteCode" value="FirstWebSiteCode"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createNewStore" after="createWebsite"> + <argument name="website" value="FirstWebSite"/> + <argument name="storeGroupName" value="NewStore"/> + <argument name="storeGroupCode" value="Base1"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView" after="createNewStore"> + <argument name="StoreGroup" value="staticFirstStoreGroup"/> + <argument name="customStore" value="staticStore"/> + </actionGroup> + + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite" after="createCustomStoreView"> + <argument name="newWebsiteName" value="SecondWebSite"/> + <argument name="websiteCode" value="SecondWebSiteCode"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStore" after="createSecondWebsite"> + <argument name="website" value="SecondWebSite"/> + <argument name="storeGroupName" value="SecondStore"/> + <argument name="storeGroupCode" value="Base2"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView2" after="createSecondStore"> + <argument name="StoreGroup" value="staticStoreGroup"/> + <argument name="customStore" value="staticSecondStore"/> + </actionGroup> + </before> + + <after> + <actionGroup ref="ResetWebUrlOptions" stepKey="resetUrlOption"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="FirstWebSite"/> + </actionGroup> + + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteSecondWebsite"> + <argument name="websiteName" value="SecondWebSite"/> + </actionGroup> + <deleteData createDataKey="category" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="product" stepKey="deleteFirstProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create product--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridColumnsInitial"/> + + <!--Open created product--> + <click selector="{{AdminProductGridSection.productGridNameProduct($$product.name$$)}}" stepKey="createdProduct"/> + <waitForPageLoad stepKey="waitForOpenedCreatedProduct"/> + + <!-- Add image to product --> + <actionGroup ref="addProductImage" stepKey="addFirstImageForProduct"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Add second image to product --> + <actionGroup ref="addProductImage" stepKey="addSecondImageForProduct"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <!--"Product in Websites": select both Websites--> + <actionGroup ref="ProductSetWebsite" stepKey="ProductSetWebsite1"> + <argument name="website" value="FirstWebSite"/> + </actionGroup> + <actionGroup ref="ProductSetWebsite" stepKey="ProductSetWebsite2"> + <argument name="website" value="SecondWebSite"/> + </actionGroup> + + <!--Go to "Catalog" -> "Products". Open created product--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoaded"/> + <click selector="{{AdminProductGridSection.productGridNameProduct($$product.name$$)}}" stepKey="openCreatedProduct"/> + <waitForPageLoad stepKey="waitForCreatedProductOpened"/> + + <!--Delete Image 1--> + <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> + + <!--Click "Save" in the upper right corner--> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> + + <!--Switch to "Store view 1"--> + <actionGroup ref="SwitchToTheNewStoreView" stepKey="selectStoreView"> + <argument name="storeViewName" value="Store View"/> + </actionGroup> + + <!-- Assert product first image not in admin product form --> + <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!--Switch to "Store view 2"--> + <actionGroup ref="SwitchToTheNewStoreView" stepKey="selectSecondStoreView"> + <argument name="storeViewName" value="Second Store View"/> + </actionGroup> + + <!-- Verify that Image 1 is deleted from the Second Store View list --> + <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInSecondStoreViewPage"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml index dc6cf012840..a33c7bb1287 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml @@ -17,33 +17,25 @@ <severity value="CRITICAL"/> <testCaseId value="MC-194"/> <group value="Catalog"/> - <skip> - <issueId value="MAGETWO-92780"/> - </skip> </annotations> <before> <createData entity="productDropDownAttribute" stepKey="attribute"/> - <createData entity="productAttributeOption1" stepKey="option1"> <requiredEntity createDataKey="attribute"/> </createData> <createData entity="productAttributeOption2" stepKey="option2"> <requiredEntity createDataKey="attribute"/> </createData> - <createData entity="AddToDefaultSet" stepKey="addToDefaultSet"> <requiredEntity createDataKey="attribute"/> </createData> - <createData entity="ApiProductWithDescription" stepKey="product"/> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> <deleteData createDataKey="attribute" stepKey="deleteAttribute"/> <actionGroup ref="logout" stepKey="logout"/> </after> - <!-- Assert attribute presence in storefront product additional information --> <amOnPage url="/$$product.custom_attributes[url_key]$$.html" stepKey="onProductPage1"/> <waitForPageLoad stepKey="wait1"/> @@ -64,6 +56,8 @@ <see userInput="$$attribute.attribute_code$$" selector="{{AdminProductAttributeSetEditSection.unassignedAttributesTree}}" stepKey="seeAttributeInUnassigned"/> <!-- Save attribute set --> <actionGroup ref="SaveAttributeSet" stepKey="SaveAttributeSet"/> + <!-- Clear cache --> + <actionGroup ref="ClearPageCacheActionGroup" stepKey="clearPageCacheActionGroup"/> <!-- Go to create new product page --> <amOnPage url="{{AdminProductCreatePage.url(AddToDefaultSet.attributeSetId, 'simple')}}" stepKey="navigateToNewProduct"/> <waitForPageLoad stepKey="wait2"/> @@ -72,8 +66,7 @@ <!-- Assert removed attribute not presence in storefront product additional information --> <amOnPage url="/$$product.custom_attributes[url_key]$$.html" stepKey="onProductPage2"/> <waitForPageLoad stepKey="wait3"/> - <actionGroup ref="checkAttributeNotInMoreInformationTab" stepKey="checkAttributeNotInMoreInformationTab"> - <argument name="attributeLabel" value="$$attribute.attribute[frontend_labels][0][label]$$"/> - </actionGroup> + <dontSeeElement selector="{{StorefrontProductMoreInformationSection.moreInformation}}" stepKey="dontSeeProductAttribute"/> + <dontSee userInput="$$attribute.attribute[frontend_labels][0][label]$$" selector="{{StorefrontProductMoreInformationSection.moreInformationTextArea}}" stepKey="dontSeeAttributeLabel"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml index 569eb290bae..386633f0e94 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml @@ -17,10 +17,6 @@ <severity value="CRITICAL"/> <group value="product"/> <testCaseId value="MAGETWO-92384"/> - <group value="skip"/> - <skip> - <issueId value="MAGETWO-93261"/> - </skip> </annotations> <before> <createData entity="_defaultCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml index 9d7c6162384..a03636e52ee 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml @@ -17,11 +17,6 @@ <description value="Admin should be able to see the full title of the selected custom option value in the order"/> <severity value="MAJOR"/> <testCaseId value="MC-3043"/> - <group value="skip"/> - <!-- Skip due to MQE-1128 --> - <skip> - <issueId value="MQE-1128"/> - </skip> </annotations> <before> <!--Create Simple Product with Custom Options--> @@ -106,6 +101,6 @@ </assertEquals> <moveMouseOver selector="{{AdminOrderItemsOrderedSection.productNameOptions}} dd" stepKey="hoverProduct"/> <waitForElementVisible selector="{{AdminOrderItemsOrderedSection.productNameOptions}} dd:nth-child(2)" stepKey="waitForCustomOptionValueFullName"/> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueDropdownLongTitle1.title}}" stepKey="seeAdminOrderProductOptionValueDropdown1"/> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj111 11Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj11111" stepKey="seeAdminOrderProductOptionValueDropdown1"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php index d8931cbbfcf..f0e17c7938b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php @@ -57,10 +57,14 @@ public function testGetCollection() $linkedProductOneMock = $this->createMock(Product::class); $linkedProductTwoMock = $this->createMock(Product::class); $linkedProductThreeMock = $this->createMock(Product::class); + $linkedProductFourMock = $this->createMock(Product::class); + $linkedProductFiveMock = $this->createMock(Product::class); $linkedProductOneMock->expects($this->once())->method('getId')->willReturn(1); $linkedProductTwoMock->expects($this->once())->method('getId')->willReturn(2); $linkedProductThreeMock->expects($this->once())->method('getId')->willReturn(3); + $linkedProductFourMock->expects($this->once())->method('getId')->willReturn(4); + $linkedProductFiveMock->expects($this->once())->method('getId')->willReturn(5); $this->converterPoolMock->expects($this->once()) ->method('getConverter') @@ -71,9 +75,11 @@ public function testGetCollection() [$linkedProductOneMock, ['name' => 'Product One', 'position' => 10]], [$linkedProductTwoMock, ['name' => 'Product Two', 'position' => 2]], [$linkedProductThreeMock, ['name' => 'Product Three', 'position' => 2]], + [$linkedProductFourMock, ['name' => 'Product Four', 'position' => null]], + [$linkedProductFiveMock, ['name' => 'Product Five']], ]; - $this->converterMock->expects($this->exactly(3))->method('convert')->willReturnMap($map); + $this->converterMock->expects($this->exactly(5))->method('convert')->willReturnMap($map); $this->providerMock->expects($this->once()) ->method('getLinkedProducts') @@ -82,14 +88,18 @@ public function testGetCollection() [ $linkedProductOneMock, $linkedProductTwoMock, - $linkedProductThreeMock + $linkedProductThreeMock, + $linkedProductFourMock, + $linkedProductFiveMock, ] ); $expectedResult = [ - 2 => ['name' => 'Product Two', 'position' => 2], - 3 => ['name' => 'Product Three', 'position' => 2], - 10 => ['name' => 'Product One', 'position' => 10], + 0 => ['name' => 'Product Four', 'position' => 0], + 1 => ['name' => 'Product Five', 'position' => 0], + 2 => ['name' => 'Product Three', 'position' => 2], + 3 => ['name' => 'Product Two', 'position' => 2], + 4 => ['name' => 'Product One', 'position' => 10], ]; $actualResult = $this->model->getCollection($this->productMock, 'crosssell'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php index cc6f5d84ef0..c04428eadef 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php @@ -54,6 +54,7 @@ public function testRemoveDeletedProducts() $productsToDeleteIds = [1, 2]; $select = $this->createMock(\Magento\Framework\DB\Select::class); $select->expects($this->once())->method('from')->with('catalog_product_entity')->will($this->returnSelf()); + $select->expects($this->once())->method('columns')->with('entity_id')->will($this->returnSelf()); $select->expects($this->once())->method('where')->with('entity_id IN(?)', $productsToDeleteIds) ->will($this->returnSelf()); $products = [['entity_id' => 2]]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php index a49fa9a15e3..85d8dd8a149 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php @@ -6,7 +6,9 @@ namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Action; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -19,45 +21,48 @@ class RowTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $store; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $productIndexerHelper; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $resource; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $connection; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $flatItemWriter; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $flatItemEraser; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $flatTableBuilder; + /** + * @inheritdoc + */ protected function setUp() { $objectManager = new ObjectManager($this); @@ -68,11 +73,11 @@ protected function setUp() $this->resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); $this->resource->expects($this->any())->method('getConnection') ->with('default') - ->will($this->returnValue($this->connection)); + ->willReturn($this->connection); $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $this->store = $this->createMock(\Magento\Store\Model\Store::class); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('store_id_1')); - $this->storeManager->expects($this->any())->method('getStores')->will($this->returnValue([$this->store])); + $this->store->expects($this->any())->method('getId')->willReturn('store_id_1'); + $this->storeManager->expects($this->any())->method('getStores')->willReturn([$this->store]); $this->flatItemEraser = $this->createMock(\Magento\Catalog\Model\Indexer\Product\Flat\Action\Eraser::class); $this->flatItemWriter = $this->createMock(\Magento\Catalog\Model\Indexer\Product\Flat\Action\Indexer::class); $this->flatTableBuilder = $this->createMock( @@ -89,9 +94,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $backendMock->expects($this->any())->method('getTable')->willReturn($attributeTable); - $statusAttributeMock->expects($this->any())->method('getBackend')->willReturn( - $backendMock - ); + $statusAttributeMock->expects($this->any())->method('getBackend')->willReturn($backendMock); $statusAttributeMock->expects($this->any())->method('getId')->willReturn($statusId); $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() @@ -102,24 +105,32 @@ protected function setUp() ['value'] )->willReturnSelf(); $selectMock->expects($this->any())->method('where')->willReturnSelf(); + $selectMock->expects($this->any())->method('order')->willReturnSelf(); + $selectMock->expects($this->any())->method('limit')->willReturnSelf(); $pdoMock = $this->createMock(\Zend_Db_Statement_Pdo::class); - $this->connection->expects($this->any()) - ->method('query') - ->with($selectMock) - ->will($this->returnValue($pdoMock)); - $pdoMock->expects($this->any())->method('fetch')->will($this->returnValue(['value' => 1])); + $this->connection->expects($this->any())->method('query')->with($selectMock)->willReturn($pdoMock); + $pdoMock->expects($this->any())->method('fetchColumn')->willReturn('1'); + + $metadataPool = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $productMetadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->getMockForAbstractClass(); + $metadataPool->expects($this->any())->method('getMetadata')->with(ProductInterface::class) + ->willReturn($productMetadata); + $productMetadata->expects($this->any())->method('getLinkField')->willReturn('entity_id'); $this->model = $objectManager->getObject( \Magento\Catalog\Model\Indexer\Product\Flat\Action\Row::class, [ - 'resource' => $this->resource, - 'storeManager' => $this->storeManager, - 'productHelper' => $this->productIndexerHelper, - 'flatItemEraser' => $this->flatItemEraser, - 'flatItemWriter' => $this->flatItemWriter, - 'flatTableBuilder' => $this->flatTableBuilder + 'resource' => $this->resource, + 'storeManager' => $this->storeManager, + 'productHelper' => $this->productIndexerHelper, + 'flatItemEraser' => $this->flatItemEraser, + 'flatItemWriter' => $this->flatItemWriter, + 'flatTableBuilder' => $this->flatTableBuilder, ] ); + + $objectManager->setBackwardCompatibleProperty($this->model, 'metadataPool', $metadataPool); } /** @@ -134,9 +145,9 @@ public function testExecuteWithEmptyId() public function testExecuteWithNonExistingFlatTablesCreatesTables() { $this->productIndexerHelper->expects($this->any())->method('getFlatTableName') - ->will($this->returnValue('store_flat_table')); + ->willReturn('store_flat_table'); $this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table') - ->will($this->returnValue(false)); + ->willReturn(false); $this->flatItemEraser->expects($this->never())->method('removeDeletedProducts'); $this->flatTableBuilder->expects($this->once())->method('build')->with('store_id_1', ['product_id_1']); $this->flatItemWriter->expects($this->once())->method('write')->with('store_id_1', 'product_id_1'); @@ -146,9 +157,9 @@ public function testExecuteWithNonExistingFlatTablesCreatesTables() public function testExecuteWithExistingFlatTablesCreatesTables() { $this->productIndexerHelper->expects($this->any())->method('getFlatTableName') - ->will($this->returnValue('store_flat_table')); + ->willReturn('store_flat_table'); $this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table') - ->will($this->returnValue(true)); + ->willReturn(true); $this->flatItemEraser->expects($this->once())->method('removeDeletedProducts'); $this->flatTableBuilder->expects($this->never())->method('build')->with('store_id_1', ['product_id_1']); $this->model->execute('product_id_1'); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index ed737df708a..681435851fb 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -118,7 +118,7 @@ private function getCacheManager() } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -130,7 +130,7 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyData(array $data) @@ -289,6 +289,7 @@ protected function customizeCategoriesField(array $meta) 'source' => 'product_details', 'displayArea' => 'insideGroup', 'sortOrder' => 20, + 'dataScope' => $fieldCode, ], ], ] diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 10251d35dff..fddc01ac4c1 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -220,4 +220,16 @@ <argument name="filter" xsi:type="object">Magento\Catalog\Ui\DataProvider\Product\AddSearchKeyConditionToCollection</argument> </arguments> </type> + <type name="Magento\Catalog\Block\Adminhtml\Product\Edit\Action\Attribute\Tab\Attributes"> + <arguments> + <argument name="excludeFields" xsi:type="array"> + <item name="0" xsi:type="string">category_ids</item> + <item name="1" xsi:type="string">gallery</item> + <item name="2" xsi:type="string">image</item> + <item name="3" xsi:type="string">media_gallery</item> + <item name="4" xsi:type="string">quantity_and_stock_status</item> + <item name="5" xsi:type="string">tier_price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 71a799fd224..d6c98b92596 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -83,8 +83,9 @@ <backend_model>Magento\Catalog\Model\Indexer\Product\Flat\System\Config\Mode</backend_model> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="default_sort_by" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="default_sort_by" translate="label comment" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Product Listing Sort by</label> + <comment>Applies to category pages</comment> <source_model>Magento\Catalog\Model\Config\Source\ListSort</source_model> </field> <field id="list_allow_all" translate="label comment" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1"> diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index 7b4e1a96add..860e5fee9f1 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="catalog_product_entity" resource="default" engine="innodb" comment="Catalog Product Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="attribute_set_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Attribute Set ID"/> <column xsi:type="varchar" name="type_id" nullable="false" length="32" default="simple" comment="Type ID"/> @@ -41,7 +41,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="datetime" name="value" on_update="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -76,7 +76,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -112,7 +112,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="int" name="value" padding="11" unsigned="false" nullable="true" identity="false" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -148,7 +148,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="text" name="value" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -183,7 +183,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="value" nullable="true" length="255" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -218,7 +218,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="int" name="position" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Position"/> <column xsi:type="varchar" name="value" nullable="true" length="255" comment="Value"/> @@ -251,7 +251,7 @@ </table> <table name="catalog_category_entity" resource="default" engine="innodb" comment="Catalog Category Table"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="attribute_set_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Attribute Set ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" @@ -286,7 +286,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="datetime" name="value" on_update="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -324,7 +324,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -363,7 +363,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="int" name="value" padding="11" unsigned="false" nullable="true" identity="false" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -402,7 +402,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="text" name="value" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -440,7 +440,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="value" nullable="true" length="255" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -738,7 +738,7 @@ <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" comment="Value ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="smallint" name="all_groups" padding="5" unsigned="true" nullable="false" identity="false" default="1" comment="Is Applicable To All Customer Groups"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" @@ -805,7 +805,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="label" nullable="true" length="255" comment="Label"/> <column xsi:type="int" name="position" padding="10" unsigned="true" nullable="true" identity="false" comment="Position"/> @@ -1651,7 +1651,8 @@ comment="Link Media value to Product entity table"> <column xsi:type="int" name="value_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Value media Entry ID"/> - <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false"/> + <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Product Entity ID"/> <constraint xsi:type="foreign" name="FK_A6C6C8FAA386736921D3A7C4B50B1185" table="catalog_product_entity_media_gallery_value_to_entity" column="value_id" referenceTable="catalog_product_entity_media_gallery" referenceColumn="value_id" diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml index 8a5f1919f78..3cbfa0f29d7 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml @@ -55,7 +55,7 @@ function bindAttributeInputType() { checkOptionsPanelVisibility(); switchDefaultValueField(); - if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price')){ + if($('frontend_input') && ($('frontend_input').value=='boolean' || $('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price')){ if($('is_filterable') && !$('is_filterable').getAttribute('readonly')){ $('is_filterable').disabled = false; } diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml index 6a5f6c46484..a7e8564e7a1 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml @@ -14,7 +14,7 @@ <% } %> <ul data-mage-init='{"menu":[]}'> <% _.each(data.items, function(value) { %> - <li <%- data.optionData(value) %>><a href="#"><%- value.label %></a></li> + <li <%= data.optionData(value) %>><a href="#"><%- value.label %></a></li> <% }); %> </ul> <% if (!data.term && data.items.length && !data.allShown()) { %> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js index 2359d1499f8..0ec404a769f 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js @@ -225,6 +225,7 @@ define([ categoryLoader.on('beforeload', function (treeLoader, node) { treeLoader.baseParams.id = node.attributes.id; + treeLoader.baseParams.selected = options.jsFormObject.updateElement.value; }); categoryLoader.on('load', function () { diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/edit-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/edit-tree.js index 3544767b8d7..7c947195f33 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/edit-tree.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/edit-tree.js @@ -18,7 +18,10 @@ require([ /** * Delete some category * This routine get categoryId explicitly, so even if currently selected tree node is out of sync - * with this form, we surely delete same category in the tree and at backend + * with this form, we surely delete same category in the tree and at backend. + * + * @deprecated + * @see deleteConfirm */ function categoryDelete(url) { confirm({ diff --git a/app/code/Magento/CatalogGraphQl/Model/Product/Option/DateType.php b/app/code/Magento/CatalogGraphQl/Model/Product/Option/DateType.php new file mode 100644 index 00000000000..e1106a3f696 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Product/Option/DateType.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Product\Option; + +use Magento\Catalog\Model\Product\Option\Type\Date as ProductDateOptionType; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Stdlib\DateTime; + +/** + * @inheritdoc + */ +class DateType extends ProductDateOptionType +{ + /** + * Make valid string as a value of date option type for GraphQl queries + * + * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) + * @return ProductDateOptionType + */ + public function validateUserValue($values) + { + if ($this->_dateExists() || $this->_timeExists()) { + return parent::validateUserValue($this->formatValues($values)); + } + + return $this; + } + + /** + * Format date value from string to date array + * + * @param [] $values + * @return [] + * @throws LocalizedException + */ + private function formatValues($values) + { + if (isset($values[$this->getOption()->getId()])) { + $value = $values[$this->getOption()->getId()]; + $dateTime = \DateTime::createFromFormat(DateTime::DATETIME_PHP_FORMAT, $value); + $values[$this->getOption()->getId()] = [ + 'date' => $value, + 'year' => $dateTime->format('Y'), + 'month' => $dateTime->format('m'), + 'day' => $dateTime->format('d'), + 'hour' => $dateTime->format('H'), + 'minute' => $dateTime->format('i'), + 'day_part' => $dateTime->format('a'), + ]; + } + + return $values; + } + + /** + * @inheritdoc + */ + public function useCalendar() + { + return false; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php index 4326a51f309..e2998438bd7 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php @@ -79,7 +79,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) @@ -114,6 +114,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $categories[$item->getId()] = $this->customAttributesFlattener ->flatten($categories[$item->getId()]); $categories[$item->getId()]['product_count'] = $item->getProductCount(); + $categories[$item->getId()]['model'] = $item; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryHtmlAttribute.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryHtmlAttribute.php new file mode 100644 index 00000000000..7ccb46c3a29 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/CategoryHtmlAttribute.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Category; + +use Magento\Catalog\Model\Category; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Catalog\Helper\Output as OutputHelper; + +/** + * Resolve rendered content for category attributes where HTML content is allowed + */ +class CategoryHtmlAttribute implements ResolverInterface +{ + /** + * @var OutputHelper + */ + private $outputHelper; + + /** + * @param OutputHelper $outputHelper + */ + public function __construct( + OutputHelper $outputHelper + ) { + $this->outputHelper = $outputHelper; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + + /* @var $category Category */ + $category = $value['model']; + $fieldName = $field->getName(); + $renderedValue = $this->outputHelper->categoryAttribute($category, $category->getData($fieldName), $fieldName); + + return $renderedValue; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 6d9f5e33dd5..4e3a8403f31 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; @@ -27,16 +28,26 @@ class CategoryTree implements ResolverInterface */ private $categoryTree; + /** + * @var ExtractDataFromCategoryTree + */ + private $extractDataFromCategoryTree; + /** * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree + * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree */ public function __construct( - \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree + \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree, + ExtractDataFromCategoryTree $extractDataFromCategoryTree ) { $this->categoryTree = $categoryTree; + $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; } /** + * Get category id + * * @param array $args * @return int * @throws GraphQlInputException @@ -62,7 +73,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $rootCategoryId = $this->getCategoryId($args); $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); if (!empty($categoriesTree)) { - return current($categoriesTree); + $result = $this->extractDataFromCategoryTree->execute($categoriesTree); + return current($result); } else { return null; } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Image.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Image.php new file mode 100644 index 00000000000..6830aecb78f --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/Image.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Product; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\ImageFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Returns product's image. If the image is not set, returns a placeholder + */ +class Image implements ResolverInterface +{ + /** + * Product image factory + * + * @var ImageFactory + */ + private $productImageFactory; + + /** + * @param ImageFactory $productImageFactory + */ + public function __construct( + ImageFactory $productImageFactory + ) { + $this->productImageFactory = $productImageFactory; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ): array { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + /** @var Product $product */ + $product = $value['model']; + $imageType = $field->getName(); + $path = $product->getData($imageType); + + $image = $this->productImageFactory->create(); + $image->setDestinationSubdir($imageType) + ->setBaseFile($path); + $imageUrl = $image->getUrl(); + + return [ + 'url' => $imageUrl, + 'path' => $path, + ]; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductHtmlAttribute.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductHtmlAttribute.php new file mode 100644 index 00000000000..43fb1355c6b --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductHtmlAttribute.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Product; + +use Magento\Catalog\Model\Product; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Catalog\Helper\Output as OutputHelper; + +/** + * Resolve rendered content for attributes where HTML content is allowed + */ +class ProductHtmlAttribute implements ResolverInterface +{ + /** + * @var OutputHelper + */ + private $outputHelper; + + /** + * @param OutputHelper $outputHelper + */ + public function __construct( + OutputHelper $outputHelper + ) { + $this->outputHelper = $outputHelper; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('"model" value should be specified')); + } + + /* @var $product Product */ + $product = $value['model']; + $fieldName = $field->getName(); + $renderedValue = $this->outputHelper->productAttribute($product, $product->getData($fieldName), $fieldName); + return $renderedValue; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 0f618058e06..d50100a65f6 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Filter; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search; @@ -51,13 +52,15 @@ class Products implements ResolverInterface * @param Builder $searchCriteriaBuilder * @param Search $searchQuery * @param Filter $filterQuery + * @param SearchFilter $searchFilter + * @param Filters $filtersDataProvider */ public function __construct( Builder $searchCriteriaBuilder, Search $searchQuery, Filter $filterQuery, SearchFilter $searchFilter, - \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider + Filters $filtersDataProvider ) { $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->searchQuery = $searchQuery; @@ -100,10 +103,10 @@ public function resolve( $currentPage = $searchCriteria->getCurrentPage(); if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) { - $currentPage = new GraphQlInputException( + throw new GraphQlInputException( __( - 'currentPage value %1 specified is greater than the number of pages available.', - [$maxPages] + 'currentPage value %1 specified is greater than the %2 page(s) available.', + [$currentPage, $maxPages] ) ); } @@ -113,7 +116,8 @@ public function resolve( 'items' => $searchResult->getProductsSearchResult(), 'page_info' => [ 'page_size' => $searchCriteria->getPageSize(), - 'current_page' => $currentPage + 'current_page' => $currentPage, + 'total_pages' => $maxPages ], 'filters' => $this->filtersDataProvider->getData($layerType) ]; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index bae9def4ee6..f2634574a2d 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -9,7 +9,6 @@ use GraphQL\Language\AST\FieldNode; use Magento\CatalogGraphQl\Model\Category\DepthCalculator; -use Magento\CatalogGraphQl\Model\Category\Hydrator; use Magento\CatalogGraphQl\Model\Category\LevelCalculator; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -17,6 +16,7 @@ use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; use Magento\CatalogGraphQl\Model\AttributesJoiner; +use Magento\Catalog\Model\Category; /** * Category tree data provider @@ -53,49 +53,52 @@ class CategoryTree */ private $metadata; - /** - * @var Hydrator - */ - private $hydrator; - /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner * @param DepthCalculator $depthCalculator * @param LevelCalculator $levelCalculator * @param MetadataPool $metadata - * @param Hydrator $hydrator */ public function __construct( CollectionFactory $collectionFactory, AttributesJoiner $attributesJoiner, DepthCalculator $depthCalculator, LevelCalculator $levelCalculator, - MetadataPool $metadata, - Hydrator $hydrator + MetadataPool $metadata ) { $this->collectionFactory = $collectionFactory; $this->attributesJoiner = $attributesJoiner; $this->depthCalculator = $depthCalculator; $this->levelCalculator = $levelCalculator; $this->metadata = $metadata; - $this->hydrator = $hydrator; } /** + * Returns categories tree starting from parent $rootCategoryId + * * @param ResolveInfo $resolveInfo * @param int $rootCategoryId - * @return array + * @return \Iterator */ - public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array + public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId): \Iterator { $categoryQuery = $resolveInfo->fieldNodes[0]; $collection = $this->collectionFactory->create(); $this->joinAttributesRecursively($collection, $categoryQuery); $depth = $this->depthCalculator->calculate($categoryQuery); $level = $this->levelCalculator->calculate($rootCategoryId); + + // If root category is being filter, we've to remove first slash + if ($rootCategoryId == Category::TREE_ROOT_ID) { + $regExpPathFilter = sprintf('.*%s/[/0-9]*$', $rootCategoryId); + } else { + $regExpPathFilter = sprintf('.*/%s/[/0-9]*$', $rootCategoryId); + } + //Search for desired part of category tree - $collection->addPathFilter(sprintf('.*/%s/[/0-9]*$', $rootCategoryId)); + $collection->addPathFilter($regExpPathFilter); + $collection->addFieldToFilter('level', ['gt' => $level]); $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); $collection->setOrder('level'); @@ -103,31 +106,12 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() . ' = ?', $rootCategoryId ); - return $this->processTree($collection->getIterator()); - } - - /** - * @param \Iterator $iterator - * @return array - */ - private function processTree(\Iterator $iterator) : array - { - $tree = []; - while ($iterator->valid()) { - /** @var CategoryInterface $category */ - $category = $iterator->current(); - $iterator->next(); - $nextCategory = $iterator->current(); - $tree[$category->getId()] = $this->hydrator->hydrateCategory($category); - if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { - $tree[$category->getId()]['children'] = $this->processTree($iterator); - } - } - - return $tree; + return $collection->getIterator(); } /** + * Join attributes recursively + * * @param Collection $collection * @param FieldNode $fieldNode * @return void diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php new file mode 100644 index 00000000000..ac8d5709c85 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; + +use Magento\CatalogGraphQl\Model\Category\Hydrator; +use Magento\Catalog\Api\Data\CategoryInterface; + +/** + * Extract data from category tree + */ +class ExtractDataFromCategoryTree +{ + /** + * @var Hydrator + */ + private $categoryHydrator; + + /** + * @param Hydrator $categoryHydrator + */ + public function __construct( + Hydrator $categoryHydrator + ) { + $this->categoryHydrator = $categoryHydrator; + } + + /** + * Extract data from category tree + * + * @param \Iterator $iterator + * @return array + */ + public function execute(\Iterator $iterator): array + { + $tree = []; + while ($iterator->valid()) { + /** @var CategoryInterface $category */ + $category = $iterator->current(); + $iterator->next(); + $nextCategory = $iterator->current(); + $tree[$category->getId()] = $this->categoryHydrator->hydrateCategory($category); + $tree[$category->getId()]['model'] = $category; + if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { + $tree[$category->getId()]['children'] = $this->execute($iterator); + } + } + + return $tree; + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 68a292ede6b..7e18ac34f0f 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -6,6 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Catalog\Model\Product\Option\Type\Date" type="Magento\CatalogGraphQl\Model\Product\Option\DateType" /> <type name="Magento\CatalogGraphQl\Model\ProductInterfaceTypeResolverComposite"> <arguments> <argument name="productTypeNameResolvers" xsi:type="array"> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 9235ec271a3..a1a587f3f37 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -248,8 +248,8 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ id: Int @doc(description: "The ID number assigned to the product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId") name: String @doc(description: "The product name. Customers use this name to identify the product.") sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer") - description: String @doc(description: "Detailed information about the product. The value can include simple HTML tags.") - short_description: String @doc(description: "A short description of the product. Its use depends on the theme.") + description: String @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductHtmlAttribute") + short_description: String @doc(description: "A short description of the product. Its use depends on the theme.") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\ProductHtmlAttribute") special_price: Float @doc(description: "The discounted price of the product") special_from_date: String @doc(description: "The beginning date that a product has a special price") special_to_date: String @doc(description: "The end date that a product has a special price") @@ -258,7 +258,7 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ meta_keyword: String @doc(description: "A comma-separated list of keywords that are visible only to search engines") meta_description: String @doc(description: "A brief overview of the product for search results listings, maximum 255 characters") image: String @doc(description: "The relative path to the main image on the product page") - small_image: String @doc(description: "The relative path to the small image, which is used on catalog pages") + small_image: ProductImage @doc(description: "The relative path to the small image, which is used on catalog pages") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Image") thumbnail: String @doc(description: "The relative path to the product's thumbnail image") new_from_date: String @doc(description: "The beginning date for new product listings, and determines if the product is featured as a new product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") new_to_date: String @doc(description: "The end date for new product listings") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\NewFromTo") @@ -352,6 +352,11 @@ type CustomizableFileValue @doc(description: "CustomizableFileValue defines the image_size_y: Int @doc(description: "The maximum height of an image") } +type ProductImage @doc(description: "Product image information. Contains image relative path and URL") { + url: String + path: String +} + interface CustomizableOptionInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CustomizableOptionTypeResolver") @doc(description: "The CustomizableOptionInterface contains basic information about a customizable option. It can be implemented by several types of configurable options.") { title: String @doc(description: "The display name for this option") required: Boolean @doc(description: "Indicates whether the option is required") @@ -364,7 +369,7 @@ interface CustomizableProductInterface @typeResolver(class: "Magento\\CatalogGra interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CategoryInterfaceTypeResolver") @doc(description: "CategoryInterface contains the full set of attributes that can be returned in a category search") { id: Int @doc(description: "An ID that uniquely identifies the category") - description: String @doc(description: "An optional description of the category") + description: String @doc(description: "An optional description of the category") @resolver(class: "\\Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\CategoryHtmlAttribute") name: String @doc(description: "The display name of the category") path: String @doc(description: "Category Path") path_in_store: String @doc(description: "Category path in store") @@ -548,6 +553,6 @@ type SortField { } type SortFields @doc(description: "SortFields contains a default value for sort fields and all available sort fields") { - default: String @doc(description: "Default value of sort fields") + default: String @doc(description: "Default value of sort fields") options: [SortField] @doc(description: "Available sort fields") } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 9877727a5af..7c56c266a64 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -5,6 +5,7 @@ */ namespace Magento\CatalogImportExport\Model\Import; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Config as CatalogConfig; use Magento\Catalog\Model\Product\Visibility; use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; @@ -15,6 +16,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Filesystem; use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor; @@ -732,6 +734,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $dateTimeFactory; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -776,7 +783,9 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param MediaGalleryProcessor $mediaProcessor * @param StockItemImporterInterface|null $stockItemImporter * @param DateTimeFactory $dateTimeFactory + * @param ProductRepositoryInterface|null $productRepository * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function __construct( \Magento\Framework\Json\Helper\Data $jsonHelper, @@ -821,7 +830,8 @@ public function __construct( ImageTypeProcessor $imageTypeProcessor = null, MediaGalleryProcessor $mediaProcessor = null, StockItemImporterInterface $stockItemImporter = null, - DateTimeFactory $dateTimeFactory = null + DateTimeFactory $dateTimeFactory = null, + ProductRepositoryInterface $productRepository = null ) { $this->_eventManager = $eventManager; $this->stockRegistry = $stockRegistry; @@ -875,6 +885,8 @@ public function __construct( ->initImagesArrayKeys(); $this->validator->init($this); $this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()->get(DateTimeFactory::class); + $this->productRepository = $productRepository ?? ObjectManager::getInstance() + ->get(ProductRepositoryInterface::class); } /** @@ -1698,6 +1710,14 @@ protected function _saveProducts() $websiteId = $this->storeResolver->getWebsiteCodeToId($websiteCode); $this->websitesCache[$rowSku][$websiteId] = true; } + } else { + $product = $this->retrieveProductBySku($rowSku); + if ($product) { + $websiteIds = $product->getWebsiteIds(); + foreach ($websiteIds as $websiteId) { + $this->websitesCache[$rowSku][$websiteId] = true; + } + } } // 3. Categories phase @@ -1996,6 +2016,11 @@ protected function processRowCategories($rowData) . ' ' . $error['exception']->getMessage() ); } + } else { + $product = $this->retrieveProductBySku($rowData['sku']); + if ($product) { + $categoryIds = $product->getCategoryIds(); + } } return $categoryIds; } @@ -2839,7 +2864,7 @@ protected function getProductUrlSuffix($storeId = null) protected function getUrlKey($rowData) { if (!empty($rowData[self::URL_KEY])) { - return strtolower($rowData[self::URL_KEY]); + return $this->productUrl->formatUrlKey($rowData[self::URL_KEY]); } if (!empty($rowData[self::COL_NAME])) { @@ -2998,4 +3023,20 @@ private function formatStockDataForRow(array $rowData): array return $row; } + + /** + * Retrieve product by sku. + * + * @param string $sku + * @return \Magento\Catalog\Api\Data\ProductInterface|null + */ + private function retrieveProductBySku($sku) + { + try { + $product = $this->productRepository->get($sku); + } catch (NoSuchEntityException $e) { + return null; + } + return $product; + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php index a5aefff656b..ffa0d4aff87 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php @@ -66,6 +66,8 @@ public function __construct( } /** + * Initialize categories to be processed + * * @return $this */ protected function initCategories() @@ -114,6 +116,9 @@ protected function createCategory($name, $parentId) if (!($parentCategory = $this->getCategoryById($parentId))) { $parentCategory = $this->categoryFactory->create()->load($parentId); } + + // Set StoreId to 0 to generate URL Keys global and prevent generating url rewrites just for default website + $category->setStoreId(0); $category->setPath($parentCategory->getPath()); $category->setParentId($parentId); $category->setName($this->unquoteDelimiter($name)); diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php index 6595aec78f5..a48b9ae5f08 100644 --- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php +++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php @@ -120,7 +120,7 @@ public function initialize( /** * if option's qty was updates we also need to update quote item qty */ - $quoteItem->setData('qty', intval($qty)); + $quoteItem->setData('qty', (int) $qty); } if ($result->getMessage() !== null) { $option->setMessage($result->getMessage()); diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php index fb6fc3be613..31fd5606a98 100644 --- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php @@ -9,9 +9,9 @@ use Magento\Catalog\Model\ProductFactory; use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface; +use Magento\Framework\DataObject\Factory as ObjectFactory; use Magento\Framework\Locale\FormatInterface; use Magento\Framework\Math\Division as MathDivision; -use Magento\Framework\DataObject\Factory as ObjectFactory; /** * Interface StockStateProvider @@ -65,6 +65,8 @@ public function __construct( } /** + * Validate stock + * * @param StockItemInterface $stockItem * @return bool */ @@ -82,6 +84,8 @@ public function verifyStock(StockItemInterface $stockItem) } /** + * Verify notification + * * @param StockItemInterface $stockItem * @return bool */ @@ -91,6 +95,8 @@ public function verifyNotification(StockItemInterface $stockItem) } /** + * Validate quote qty + * * @param StockItemInterface $stockItem * @param int|float $qty * @param int|float $summaryQty @@ -113,13 +119,13 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQ $result->setItemIsQtyDecimal($stockItem->getIsQtyDecimal()); if (!$stockItem->getIsQtyDecimal()) { $result->setHasQtyOptionUpdate(true); - $qty = intval($qty); + $qty = (int) $qty; /** * Adding stock data to quote item */ $result->setItemQty($qty); $qty = $this->getNumber($qty); - $origQty = intval($origQty); + $origQty = (int) $origQty; $result->setOrigQty($origQty); } @@ -254,6 +260,8 @@ public function checkQty(StockItemInterface $stockItem, $qty) } /** + * Returns suggested qty + * * Returns suggested qty that satisfies qty increments and minQty/maxQty/minSaleQty/maxSaleQty conditions * or original qty if such value does not exist * @@ -294,6 +302,8 @@ public function suggestQty(StockItemInterface $stockItem, $qty) } /** + * Check Qty Increments + * * @param StockItemInterface $stockItem * @param float|int $qty * @return \Magento\Framework\DataObject @@ -369,6 +379,8 @@ public function getStockQty(StockItemInterface $stockItem) } /** + * Get numeric qty + * * @param string|float|int|null $qty * @return float|null */ diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml index f8571b6e332..0a7f0fdc32d 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml @@ -288,7 +288,7 @@ <settings> <scopeLabel>[GLOBAL]</scopeLabel> <validation> - <rule name="validate-number" xsi:type="boolean">true</rule> + <rule name="validate-greater-than-zero" xsi:type="boolean">true</rule> </validation> <label translate="true">Maximum Qty Allowed in Shopping Cart</label> <dataScope>max_sale_qty</dataScope> diff --git a/app/code/Magento/CatalogRule/Block/Adminhtml/Edit/DeleteButton.php b/app/code/Magento/CatalogRule/Block/Adminhtml/Edit/DeleteButton.php index 6390822b58f..184cb641929 100644 --- a/app/code/Magento/CatalogRule/Block/Adminhtml/Edit/DeleteButton.php +++ b/app/code/Magento/CatalogRule/Block/Adminhtml/Edit/DeleteButton.php @@ -25,7 +25,7 @@ public function getButtonData() 'class' => 'delete', 'on_click' => 'deleteConfirm(\'' . __( 'Are you sure you want to do this?' - ) . '\', \'' . $this->urlBuilder->getUrl('*/*/delete', ['id' => $ruleId]) . '\')', + ) . '\', \'' . $this->urlBuilder->getUrl('*/*/delete', ['id' => $ruleId]) . '\', {data: {}})', 'sort_order' => 20, ]; } diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php index 3500506d8d6..998d45b839c 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php @@ -6,9 +6,10 @@ */ namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; -class Delete extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog +class Delete extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog implements HttpPostActionInterface { /** * @return void diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php index 945c28b2088..2c2abcef8b2 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog; -class Edit extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Index.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Index.php index 8c6a3881951..a9dcd1f6383 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Index.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog; -class Index extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewAction.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewAction.php index c677c75c595..d86c56402d2 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewAction.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog; -class NewAction extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php index b790d90d380..a845c104f94 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php @@ -6,9 +6,12 @@ */ namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Rule\Model\Condition\AbstractCondition; +use Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog as CatalogAction; -class NewConditionHtml extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog +class NewConditionHtml extends CatalogAction implements HttpPostActionInterface, HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php index f3046c58a38..bad1118e3ae 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php @@ -6,6 +6,7 @@ */ namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; @@ -15,7 +16,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog +class Save extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog implements HttpPostActionInterface { /** * @var DataPersistorInterface diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php index d049d74bd26..18a58c0f854 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\Backend\App\Action\Context; use Magento\Catalog\Model\Category; use Magento\Framework\Registry; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; -class CategoriesJson extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Widget +/** + * Categories json widget for catalog rule + */ +class CategoriesJson extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Widget implements HttpPostActionInterface { /** * Core registry @@ -77,10 +80,11 @@ public function execute() if (!($category = $this->_initCategory())) { return; } + $selected = $this->getRequest()->getPost('selected', ''); $block = $this->_view->getLayout()->createBlock( \Magento\Catalog\Block\Adminhtml\Category\Checkboxes\Tree::class )->setCategoryIds( - [$categoryId] + explode(',', $selected) ); $this->getResponse()->representJson( $block->getTreeJson($category) diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml index 716a363ec5d..741da96179b 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -16,9 +16,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-74"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <createData entity="ApiCategory" stepKey="createCategoryOne"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml index 072385c4664..befe0b0ce7f 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml @@ -17,9 +17,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-65"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <!-- Create the simple product and category that it will be in --> @@ -77,9 +74,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-93"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> @@ -103,9 +97,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-69"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> @@ -129,9 +120,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-60"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> @@ -155,9 +143,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-71"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <!-- Create a simple product and a category--> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml index 83770ffff5e..d3546d06492 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml @@ -17,9 +17,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-160"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index 55f775e40fe..e7be6e8443a 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -17,9 +17,6 @@ <severity value="CRITICAL"/> <testCaseId value="MC-79"/> <group value="CatalogRule"/> - <skip> - <issueId value="DEVOPS-3618"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="login"/> diff --git a/app/code/Magento/CatalogRule/Test/Unit/Block/Adminhtml/Edit/DeleteButtonTest.php b/app/code/Magento/CatalogRule/Test/Unit/Block/Adminhtml/Edit/DeleteButtonTest.php index 6178d51644f..f969b342f82 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Block/Adminhtml/Edit/DeleteButtonTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Block/Adminhtml/Edit/DeleteButtonTest.php @@ -24,13 +24,16 @@ class DeleteButtonTest extends \PHPUnit\Framework\TestCase */ protected $registryMock; + /** + * @inheritDoc + */ protected function setUp() { $this->urlBuilderMock = $this->createMock(\Magento\Framework\UrlInterface::class); $this->registryMock = $this->createMock(\Magento\Framework\Registry::class); $contextMock = $this->createMock(\Magento\Backend\Block\Widget\Context::class); - $contextMock->expects($this->once())->method('getUrlBuilder')->willReturn($this->urlBuilderMock); + $contextMock->expects($this->any())->method('getUrlBuilder')->willReturn($this->urlBuilderMock); $this->model = new \Magento\CatalogRule\Block\Adminhtml\Edit\DeleteButton( $contextMock, @@ -38,33 +41,9 @@ protected function setUp() ); } - public function testGetButtonData() - { - $ruleId = 42; - $deleteUrl = 'http://magento.com/rule/delete/' . $ruleId; - $ruleMock = new \Magento\Framework\DataObject(['id' => $ruleId]); - - $this->registryMock->expects($this->once()) - ->method('registry') - ->with(RegistryConstants::CURRENT_CATALOG_RULE_ID) - ->willReturn($ruleMock); - $this->urlBuilderMock->expects($this->once()) - ->method('getUrl') - ->with('*/*/delete', ['id' => $ruleId]) - ->willReturn($deleteUrl); - - $data = [ - 'label' => __('Delete Rule'), - 'class' => 'delete', - 'on_click' => 'deleteConfirm(\'' . __( - 'Are you sure you want to do this?' - ) . '\', \'' . $deleteUrl . '\')', - 'sort_order' => 20, - ]; - - $this->assertEquals($data, $this->model->getButtonData()); - } - + /** + * Test empty response without a present rule. + */ public function testGetButtonDataWithoutRule() { $this->assertEquals([], $this->model->getButtonData()); diff --git a/app/code/Magento/CatalogRule/etc/db_schema.xml b/app/code/Magento/CatalogRule/etc/db_schema.xml index f4c40a6930c..25c9d866167 100644 --- a/app/code/Magento/CatalogRule/etc/db_schema.xml +++ b/app/code/Magento/CatalogRule/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="catalogrule" resource="default" engine="innodb" comment="CatalogRule"> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Name"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="date" name="from_date" comment="From"/> @@ -39,7 +39,7 @@ <column xsi:type="int" name="rule_product_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Rule Product Id"/> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="int" name="from_time" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="From Time"/> <column xsi:type="int" name="to_time" padding="10" unsigned="true" nullable="false" identity="false" default="0" @@ -119,7 +119,7 @@ </table> <table name="catalogrule_group_website" resource="default" engine="innodb" comment="CatalogRule Group Website"> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Customer Group Id"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" @@ -137,7 +137,8 @@ </index> </table> <table name="catalogrule_website" resource="default" engine="innodb" comment="Catalog Rules To Websites Relations"> - <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false"/> + <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Rule ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website Id"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -156,7 +157,8 @@ </table> <table name="catalogrule_customer_group" resource="default" engine="innodb" comment="Catalog Rules To Customer Groups Relations"> - <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false"/> + <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Rule ID"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Customer Group Id"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -177,7 +179,7 @@ <column xsi:type="int" name="rule_product_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Rule Product Id"/> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="int" name="from_time" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="From Time"/> <column xsi:type="int" name="to_time" padding="10" unsigned="true" nullable="false" identity="false" default="0" @@ -259,7 +261,7 @@ <table name="catalogrule_group_website_replica" resource="default" engine="innodb" comment="CatalogRule Group Website"> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Customer Group Id"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Index.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Index.php index c04593a5498..a669016eb6b 100644 --- a/app/code/Magento/CatalogSearch/Controller/Advanced/Index.php +++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Index.php @@ -6,13 +6,15 @@ */ namespace Magento\CatalogSearch\Controller\Advanced; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; /** * @deprecated CatalogSearch will be removed in 2.4, and {@see \Magento\ElasticSearch} * will replace it as the default search engine. */ -class Index extends \Magento\Framework\App\Action\Action +class Index extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface { /** * @return \Magento\Framework\Controller\ResultInterface diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php index 2862efe4b4c..937175511b1 100644 --- a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php +++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php @@ -6,15 +6,17 @@ */ namespace Magento\CatalogSearch\Controller\Advanced; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\CatalogSearch\Model\Advanced as ModelAdvanced; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\UrlFactory; /** * @deprecated CatalogSearch will be removed in 2.4, and {@see \Magento\ElasticSearch} * will replace it as the default search engine. */ -class Result extends \Magento\Framework\App\Action\Action +class Result extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface { /** * Url factory diff --git a/app/code/Magento/CatalogSearch/Controller/Result/Index.php b/app/code/Magento/CatalogSearch/Controller/Result/Index.php index ab796e12d81..7c3577df452 100644 --- a/app/code/Magento/CatalogSearch/Controller/Result/Index.php +++ b/app/code/Magento/CatalogSearch/Controller/Result/Index.php @@ -6,9 +6,11 @@ */ namespace Magento\CatalogSearch\Controller\Result; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Catalog\Model\Layer\Resolver; use Magento\Catalog\Model\Session; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Search\Model\QueryFactory; use Magento\Search\Model\PopularSearchTerms; @@ -17,7 +19,7 @@ * @deprecated CatalogSearch will be removed in 2.4, and {@see \Magento\ElasticSearch} * will replace it as the default search engine. */ -class Index extends \Magento\Framework\App\Action\Action +class Index extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface, HttpPostActionInterface { /** * Catalog session diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php new file mode 100644 index 00000000000..b12da6243a9 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Test\Unit\Observer; + +use Magento\Catalog\Model\Category; +use Magento\CatalogUrlRewrite\Block\UrlKeyRenderer; +use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; +use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap; +use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer; +use Magento\CatalogUrlRewrite\Observer\CategoryProcessUrlRewriteMovingObserver; +use Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\UrlRewrite\Model\UrlPersistInterface; + +/** + * Class CategoryProcessUrlRewriteMovingObserverTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CategoryProcessUrlRewriteMovingObserverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var CategoryProcessUrlRewriteMovingObserver + */ + private $observer; + + /** + * @var CategoryUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + private $categoryUrlRewriteGeneratorMock; + + /** + * @var UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlPersistMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var UrlRewriteHandler|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlRewriteHandlerMock; + + /** + * @var DatabaseMapPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $databaseMapPoolMock; + + /** + * Set Up + */ + protected function setUp() + { + $this->categoryUrlRewriteGeneratorMock = $this->createMock(CategoryUrlRewriteGenerator::class); + $this->urlPersistMock = $this->createMock(UrlPersistInterface::class); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + $this->urlRewriteHandlerMock = $this->createMock(UrlRewriteHandler::class); + /** @var UrlRewriteBunchReplacer|\PHPUnit_Framework_MockObject_MockObject $urlRewriteMock */ + $urlRewriteMock = $this->createMock(UrlRewriteBunchReplacer::class); + $this->databaseMapPoolMock = $this->createMock(DatabaseMapPool::class); + + $this->observer = new CategoryProcessUrlRewriteMovingObserver( + $this->categoryUrlRewriteGeneratorMock, + $this->urlPersistMock, + $this->scopeConfigMock, + $this->urlRewriteHandlerMock, + $urlRewriteMock, + $this->databaseMapPoolMock, + [ + DataCategoryUrlRewriteDatabaseMap::class, + DataProductUrlRewriteDatabaseMap::class + ] + ); + } + + /** + * Test category process rewrite url by changing the parent + * + * @return void + */ + public function testCategoryProcessUrlRewriteAfterMovingWithChangedParentId() + { + /** @var Observer|\PHPUnit_Framework_MockObject_MockObject $observerMock */ + $observerMock = $this->createMock(Observer::class); + $eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getCategory']) + ->getMock(); + $categoryMock = $this->createPartialMock(Category::class, [ + 'dataHasChangedFor', + 'getEntityId', + 'getStoreId', + 'setData' + ]); + + $categoryMock->expects($this->once())->method('dataHasChangedFor')->with('parent_id') + ->willReturn(true); + $eventMock->expects($this->once())->method('getCategory')->willReturn($categoryMock); + $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $this->scopeConfigMock->expects($this->once())->method('isSetFlag') + ->with(UrlKeyRenderer::XML_PATH_SEO_SAVE_HISTORY)->willReturn(true); + $this->categoryUrlRewriteGeneratorMock->expects($this->once())->method('generate') + ->with($categoryMock, true)->willReturn(['category-url-rewrite']); + $this->urlRewriteHandlerMock->expects($this->once())->method('generateProductUrlRewrites') + ->with($categoryMock)->willReturn(['product-url-rewrite']); + $this->databaseMapPoolMock->expects($this->exactly(2))->method('resetMap')->willReturnSelf(); + + $this->observer->execute($observerMock); + } + + /** + * Test category process rewrite url without changing the parent + * + * @return void + */ + public function testCategoryProcessUrlRewriteAfterMovingWithinNotChangedParent() + { + /** @var Observer|\PHPUnit_Framework_MockObject_MockObject $observerMock */ + $observerMock = $this->createMock(Observer::class); + $eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getCategory']) + ->getMock(); + $categoryMock = $this->createPartialMock(Category::class, ['dataHasChangedFor']); + $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getCategory')->willReturn($categoryMock); + $categoryMock->expects($this->once())->method('dataHasChangedFor')->with('parent_id') + ->willReturn(false); + + $this->observer->execute($observerMock); + } +} diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml index be4bb9fcd70..e6e0f6cf721 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/module.xml @@ -6,5 +6,10 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_CatalogUrlRewriteGraphQl" /> + <module name="Magento_CatalogUrlRewriteGraphQl" > + <sequence> + <module name="Magento_UrlRewriteGraphQl"/> + <module name="Magento_CatalogGraphQl"/> + </sequence> + </module> </config> diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls index b96cfcb03d4..f4ad2e930dd 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls @@ -4,6 +4,7 @@ interface ProductInterface { url_key: String @doc(description: "The part of the URL that identifies the product") url_path: String @doc(description: "The part of the URL that precedes the url_key") + url_rewrites: [UrlRewrite] @doc(description: "URL rewrites list") @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") } input ProductFilterInput { diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 9a55f981b76..b138ef765a7 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -14,6 +14,7 @@ /** * Catalog Products List widget block + * * Class ProductsList * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -130,7 +131,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function _construct() { @@ -164,7 +165,7 @@ public function getCacheKeyInfo() $this->_storeManager->getStore()->getId(), $this->_design->getDesignTheme()->getId(), $this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP), - intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)), + (int) $this->getRequest()->getParam($this->getData('page_var_name'), 1), $this->getProductsPerPage(), $conditions, $this->json->serialize($this->getRequest()->getParams()), @@ -174,7 +175,7 @@ public function getCacheKeyInfo() } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.NPathComplexity) */ public function getProductPriceHtml( @@ -198,20 +199,25 @@ public function getProductPriceHtml( /** @var \Magento\Framework\Pricing\Render $priceRender */ $priceRender = $this->getLayout()->getBlock('product.price.render.default'); - - $price = ''; - if ($priceRender) { - $price = $priceRender->render( - \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE, - $product, - $arguments + if (!$priceRender) { + $priceRender = $this->getLayout()->createBlock( + \Magento\Framework\Pricing\Render::class, + 'product.price.render.default', + ['data' => ['price_render_handle' => 'catalog_product_prices']] ); } + + $price = $priceRender->render( + \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE, + $product, + $arguments + ); + return $price; } /** - * {@inheritdoc} + * @inheritdoc */ protected function _beforeToHtml() { @@ -249,6 +255,8 @@ public function createCollection() } /** + * Returns conditions + * * @return \Magento\Rule\Model\Condition\Combine */ protected function getConditions() @@ -386,8 +394,9 @@ public function getTitle() } /** - * @return PriceCurrencyInterface + * Returns PriceCurrencyInterface instance * + * @return PriceCurrencyInterface * @deprecated 100.2.0 */ private function getPriceCurrency() diff --git a/app/code/Magento/Checkout/Controller/Account/DelegateCreate.php b/app/code/Magento/Checkout/Controller/Account/DelegateCreate.php index 6c4c8b053e2..e4f909f9a81 100644 --- a/app/code/Magento/Checkout/Controller/Account/DelegateCreate.php +++ b/app/code/Magento/Checkout/Controller/Account/DelegateCreate.php @@ -7,6 +7,7 @@ namespace Magento\Checkout\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; use Magento\Checkout\Model\Session; @@ -15,7 +16,7 @@ /** * Redirect guest customer for registration. */ -class DelegateCreate extends Action +class DelegateCreate extends Action implements HttpGetActionInterface { /** * @var OrderCustomerDelegateInterface diff --git a/app/code/Magento/Checkout/Controller/Cart/Add.php b/app/code/Magento/Checkout/Controller/Cart/Add.php index 4a976624602..739f71caeb8 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Add.php +++ b/app/code/Magento/Checkout/Controller/Cart/Add.php @@ -6,6 +6,7 @@ */ namespace Magento\Checkout\Controller\Cart; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Checkout\Model\Cart as CustomerCart; use Magento\Framework\Exception\NoSuchEntityException; @@ -15,7 +16,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Add extends \Magento\Checkout\Controller\Cart +class Add extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface { /** * @var ProductRepositoryInterface diff --git a/app/code/Magento/Checkout/Controller/Cart/Configure.php b/app/code/Magento/Checkout/Controller/Cart/Configure.php index 19b2d2db345..aa4ae755d79 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Configure.php +++ b/app/code/Magento/Checkout/Controller/Cart/Configure.php @@ -7,13 +7,14 @@ namespace Magento\Checkout\Controller\Cart; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework; use Magento\Framework\Controller\ResultFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Configure extends \Magento\Checkout\Controller\Cart +class Configure extends \Magento\Checkout\Controller\Cart implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Checkout/Controller/Cart/CouponPost.php b/app/code/Magento/Checkout/Controller/Cart/CouponPost.php index 5f683351811..fdfe817f354 100644 --- a/app/code/Magento/Checkout/Controller/Cart/CouponPost.php +++ b/app/code/Magento/Checkout/Controller/Cart/CouponPost.php @@ -5,10 +5,12 @@ */ namespace Magento\Checkout\Controller\Cart; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CouponPost extends \Magento\Checkout\Controller\Cart +class CouponPost extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface { /** * Sales quote repository diff --git a/app/code/Magento/Checkout/Controller/Cart/Delete.php b/app/code/Magento/Checkout/Controller/Cart/Delete.php index 4a6174e83fd..7e88a484f9f 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Delete.php +++ b/app/code/Magento/Checkout/Controller/Cart/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\Checkout\Controller\Cart; -class Delete extends \Magento\Checkout\Controller\Cart +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Delete extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface { /** * Delete shopping cart item action diff --git a/app/code/Magento/Checkout/Controller/Cart/Index.php b/app/code/Magento/Checkout/Controller/Cart/Index.php index 3fb582d35e2..182ab677777 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Index.php +++ b/app/code/Magento/Checkout/Controller/Cart/Index.php @@ -7,7 +7,9 @@ namespace Magento\Checkout\Controller\Cart; -class Index extends \Magento\Checkout\Controller\Cart +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Checkout\Controller\Cart implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php index 59bd6489bf9..40ce2252581 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php @@ -7,7 +7,9 @@ namespace Magento\Checkout\Controller\Cart; -class UpdateItemOptions extends \Magento\Checkout\Controller\Cart +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class UpdateItemOptions extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface { /** * Update product configuration for a cart item diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php b/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php index 05ce48c9b46..9f0dcc6d9c1 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php @@ -7,8 +7,9 @@ namespace Magento\Checkout\Controller\Cart; use Magento\Checkout\Model\Cart\RequestQuantityProcessor; +use Magento\Framework\App\Action\HttpPostActionInterface; -class UpdatePost extends \Magento\Checkout\Controller\Cart +class UpdatePost extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface { /** * @var RequestQuantityProcessor diff --git a/app/code/Magento/Checkout/Controller/Index/Index.php b/app/code/Magento/Checkout/Controller/Index/Index.php index 785c1f1473b..5acfab435b5 100644 --- a/app/code/Magento/Checkout/Controller/Index/Index.php +++ b/app/code/Magento/Checkout/Controller/Index/Index.php @@ -9,7 +9,9 @@ namespace Magento\Checkout\Controller\Index; -class Index extends \Magento\Checkout\Controller\Onepage +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Checkout\Controller\Onepage implements HttpGetActionInterface { /** * Checkout page diff --git a/app/code/Magento/Checkout/Controller/Onepage/Success.php b/app/code/Magento/Checkout/Controller/Onepage/Success.php index ae9be42a89c..7db5cd8f012 100644 --- a/app/code/Magento/Checkout/Controller/Onepage/Success.php +++ b/app/code/Magento/Checkout/Controller/Onepage/Success.php @@ -6,7 +6,9 @@ */ namespace Magento\Checkout\Controller\Onepage; -class Success extends \Magento\Checkout\Controller\Onepage +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Success extends \Magento\Checkout\Controller\Onepage implements HttpGetActionInterface { /** * Order success action diff --git a/app/code/Magento/Checkout/Controller/Sidebar/RemoveItem.php b/app/code/Magento/Checkout/Controller/Sidebar/RemoveItem.php index c84aec336a5..f589e702de9 100644 --- a/app/code/Magento/Checkout/Controller/Sidebar/RemoveItem.php +++ b/app/code/Magento/Checkout/Controller/Sidebar/RemoveItem.php @@ -5,7 +5,9 @@ */ namespace Magento\Checkout\Controller\Sidebar; -class RemoveItem extends \Magento\Framework\App\Action\Action +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class RemoveItem extends \Magento\Framework\App\Action\Action implements HttpPostActionInterface { /** * @var \Magento\Checkout\Model\Sidebar diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml new file mode 100644 index 00000000000..354ad6d2b44 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <!--Fill shipment form for free shipping--> + <actionGroup name="ShipmentFormFreeShippingActionGroup"> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="setCustomerEmail"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="SetCustomerFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="SetCustomerLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{CustomerAddressSimple.street}}" stepKey="SetCustomerStreetAddress"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="SetCustomerCity"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="SetCustomerZipCode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="SetCustomerPhoneNumber"/> + <click selector="{{CheckoutShippingSection.region}}" stepKey="clickToSetState"/> + <click selector="{{CheckoutShippingSection.state}}" stepKey="clickToChooseState"/> + <see userInput="$0.00 Free Free Shipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Free Shipping')}}" stepKey="seeShippingMethod" after="clickToChooseState"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Free Shipping')}}" stepKey="selectFlatShippingMethod" after="seeShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask" after="selectFlatShippingMethod"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickToSaveShippingInfo"/> + <waitForPageLoad time="5" stepKey="waitForReviewAndPaymentsPageIsLoaded"/> + <seeInCurrentUrl url="payment" stepKey="reviewAndPaymentIsShown"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml index 494a365ffd5..ff7f8995c06 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml @@ -31,6 +31,7 @@ <element name="next" type="button" selector="button.button.action.continue.primary" timeout="30"/> <element name="firstShippingMethod" type="radio" selector="//*[@id='checkout-shipping-method-load']//input[@class='radio']"/> <element name="defaultShipping" type="button" selector=".billing-address-details"/> + <element name="state" type="button" selector="//*[text()='Alabama']"/> <element name="stateInput" type="input" selector="input[name=region]"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml new file mode 100644 index 00000000000..aaedaedb723 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ZeroSubtotalOrdersWithProcessingStatusTest"> + <annotations> + <features value="Checkout"/> + <stories value="MAGETWO-71375: Zero Subtotal Orders have incorrect status"/> + <title value="Checking status of Zero Subtotal Orders with 'Processing' New Order Status"/> + <description value="Created order should be in Processing status"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94178"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="simplecategory"/> + <createData entity="SimpleProduct" stepKey="simpleproduct"> + <requiredEntity createDataKey="simplecategory"/> + </createData> + <createData entity="PaymentMethodsSettingConfig" stepKey="paymentMethodsSettingConfig"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <!--Go to Admin page--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteSalesRule"> + <argument name="ruleName" value="{{ApiSalesRule.name}}"/> + </actionGroup> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DisableFreeShippingConfig" stepKey="disableFreeShippingConfig"/> + <createData entity="DisablePaymentMethodsSettingConfig" stepKey="disablePaymentMethodsSettingConfig"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="simpleproduct" stepKey="deleteProduct"/> + <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> + </after> + + <!--Open MARKETING > Cart Price Rules--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + + <!--Add New Rule--> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ApiSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsite"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="chooseNotLoggedInCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="Specific Coupon" stepKey="selectCouponType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="{{_defaultCoupon.code}}" stepKey="fillCouponCode"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCoupon}}" userInput="99" stepKey="fillUserPerCoupon"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Percent of product price discount" stepKey="selectActionType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="100" stepKey="fillDiscountAmount"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!--Proceed to store front and place an order with free shipping using created coupon--> + <!--Add product to card--> + <actionGroup ref="AddSimpleProductToCart" stepKey="AddProductToCard"> + <argument name="product" value="$$simpleproduct$$"/> + </actionGroup> + + <!--Proceed to shipment--> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickToOpenCard"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="clickToProceedToCheckout"/> + <waitForPageLoad stepKey="waitForTheFormIsOpened"/> + + <!--Fill shipping form--> + <actionGroup ref="ShipmentFormFreeShippingActionGroup" stepKey="shipmentFormFreeShippingActionGroup"/> + + <click selector="{{DiscountSection.DiscountTab}}" stepKey="clickToAddDiscount"/> + <fillField selector="{{DiscountSection.DiscountInput}}" userInput="{{_defaultCoupon.code}}" stepKey="TypeDiscountCode"/> + <click selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="clickToApplyDiscount"/> + <waitForPageLoad stepKey="WaitForDiscountToBeAdded"/> + <see userInput="Your coupon was successfully applied." stepKey="verifyText"/> + + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!--Proceed to Admin panel > SALES > Orders. Created order should be in Processing status--> + <amOnPage url="/admin/sales/order/" stepKey="navigateToSalesOrderPage"/> + <waitForPageLoad stepKey="waitForSalesOrderPageLoaded"/> + + <!-- Open Order --> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForCreatedOrderPageOpened"/> + + <!--Verify that Created order is in Processing status--> + <see selector="{{AdminShipmentOrderInformationSection.orderStatus}}" userInput="Processing" stepKey="seeShipmentOrderStatus"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js b/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js index 848a7daf71e..16fc459729b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js @@ -8,8 +8,9 @@ */ define([ 'mage/url', - 'Magento_Ui/js/model/messageList' -], function (url, globalMessageList) { + 'Magento_Ui/js/model/messageList', + 'consoleLogger' +], function (url, globalMessageList, consoleLogger) { 'use strict'; return { @@ -25,8 +26,12 @@ define([ if (response.status == 401) { //eslint-disable-line eqeqeq window.location.replace(url.build('customer/account/login/')); } else { - error = JSON.parse(response.responseText); - messageContainer.addErrorMessage(error); + try { + error = JSON.parse(response.responseText); + messageContainer.addErrorMessage(error); + } catch (e) { + consoleLogger.error(e); + } } } }; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js index 39b5ea0299a..a857d89a72b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js @@ -55,6 +55,12 @@ define( checkoutDataResolver.resolveEstimationAddress(); address = quote.isVirtual() ? quote.billingAddress() : quote.shippingAddress(); + if (!address && quote.isVirtual()) { + address = addressConverter.formAddressDataToQuoteAddress( + checkoutData.getSelectedBillingAddress() + ); + } + if (address) { estimatedAddress = address.isEditable() ? addressConverter.quoteAddressToFormAddressData(address) : diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html index cc1d960bbe4..ea521b3a8af 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html @@ -8,7 +8,7 @@ <text args="currentBillingAddress().prefix"/> <text args="currentBillingAddress().firstname"/> <text args="currentBillingAddress().middlename"/> <text args="currentBillingAddress().lastname"/> <text args="currentBillingAddress().suffix"/><br/> <text args="_.values(currentBillingAddress().street).join(', ')"/><br/> - <text args="currentBillingAddress().city "/>, <span html="currentBillingAddress().region"></span> <text args="currentBillingAddress().postcode"/><br/> + <text args="currentBillingAddress().city "/>, <span text="currentBillingAddress().region"></span> <text args="currentBillingAddress().postcode"/><br/> <text args="getCountryName(currentBillingAddress().countryId)"/><br/> <a if="currentBillingAddress().telephone" attr="'href': 'tel:' + currentBillingAddress().telephone" text="currentBillingAddress().telephone"></a><br/> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html index 05ced7a978f..2a5dc27328a 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html @@ -8,7 +8,7 @@ <text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/> <text args="address().lastname"/> <text args="address().suffix"/><br/> <text args="_.values(address().street).join(', ')"/><br/> - <text args="address().city "/>, <span html="address().region"></span> <text args="address().postcode"/><br/> + <text args="address().city "/>, <span text="address().region"></span> <text args="address().postcode"/><br/> <text args="getCountryName(address().countryId)"/><br/> <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html index 97286a28552..541413955cb 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html @@ -8,7 +8,7 @@ <text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/> <text args="address().lastname"/> <text args="address().suffix"/><br/> <text args="_.values(address().street).join(', ')"/><br/> - <text args="address().city "/>, <span html="address().region"></span> <text args="address().postcode"/><br/> + <text args="address().city "/>, <span text="address().region"></span> <text args="address().postcode"/><br/> <text args="getCountryName(address().countryId)"/><br/> <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/> diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php index f7b178df996..d727107a86f 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php @@ -12,8 +12,9 @@ use Magento\Framework\Registry; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\Action\HttpPostActionInterface; -class Delete extends Agreement +class Delete extends Agreement implements HttpPostActionInterface { /** * @var CheckoutAgreementsRepositoryInterface diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php index 8bec3b581cd..1a82108a3da 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php @@ -12,8 +12,9 @@ use Magento\Framework\Registry; use Magento\Framework\App\ObjectManager; use Magento\CheckoutAgreements\Block\Adminhtml\Agreement\Edit as BlockEdit; +use Magento\Framework\App\Action\HttpGetActionInterface; -class Edit extends Agreement +class Edit extends Agreement implements HttpGetActionInterface { /** * @var AgreementFactory diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Index.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Index.php index b1dfd8c304d..d32ee7a4b75 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Index.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; -class Index extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/NewAction.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/NewAction.php index 4d482ebfc20..caa21c46823 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/NewAction.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; -class NewAction extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php index 05a16d3dd42..4d4dd076ea1 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php @@ -13,8 +13,9 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\Action\HttpPostActionInterface; -class Save extends Agreement +class Save extends Agreement implements HttpPostActionInterface { /** * @var AgreementFactory diff --git a/app/code/Magento/Cms/Block/Widget/Block.php b/app/code/Magento/Cms/Block/Widget/Block.php index d8bd483fae5..aa6aeaff4ec 100644 --- a/app/code/Magento/Cms/Block/Widget/Block.php +++ b/app/code/Magento/Cms/Block/Widget/Block.php @@ -3,14 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Cms\Block\Widget; +use Magento\Framework\DataObject\IdentityInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Cms\Model\Block as CmsBlock; +use Magento\Widget\Block\BlockInterface; + /** * Cms Static Block Widget * - * @author Magento Core Team <core@magentocommerce.com> + * @author Magento Core Team <core@magentocommerce.com> */ -class Block extends \Magento\Framework\View\Element\Template implements \Magento\Widget\Block\BlockInterface +class Block extends \Magento\Framework\View\Element\Template implements BlockInterface, IdentityInterface { /** * @var \Magento\Cms\Model\Template\FilterProvider @@ -31,6 +39,11 @@ class Block extends \Magento\Framework\View\Element\Template implements \Magento */ protected $_blockFactory; + /** + * @var CmsBlock + */ + private $block; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Cms\Model\Template\FilterProvider $filterProvider @@ -49,8 +62,9 @@ public function __construct( } /** - * Prepare block text and determine whether block output enabled or not - * Prevent blocks recursion if needed + * Prepare block text and determine whether block output enabled or not. + * + * Prevent blocks recursion if needed. * * @return $this */ @@ -65,19 +79,63 @@ protected function _beforeToHtml() } self::$_widgetUsageMap[$blockHash] = true; - if ($blockId) { - $storeId = $this->_storeManager->getStore()->getId(); - /** @var \Magento\Cms\Model\Block $block */ - $block = $this->_blockFactory->create(); - $block->setStoreId($storeId)->load($blockId); - if ($block->isActive()) { + $block = $this->getBlock(); + + if ($block && $block->isActive()) { + try { + $storeId = $this->_storeManager->getStore()->getId(); $this->setText( $this->_filterProvider->getBlockFilter()->setStoreId($storeId)->filter($block->getContent()) ); + } catch (NoSuchEntityException $e) { } } - unset(self::$_widgetUsageMap[$blockHash]); return $this; } + + /** + * Get identities of the Cms Block + * + * @return array + */ + public function getIdentities() + { + $block = $this->getBlock(); + + if ($block) { + return $block->getIdentities(); + } + + return []; + } + + /** + * Get block + * + * @return CmsBlock|null + */ + private function getBlock(): ?CmsBlock + { + if ($this->block) { + return $this->block; + } + + $blockId = $this->getData('block_id'); + + if ($blockId) { + try { + $storeId = $this->_storeManager->getStore()->getId(); + /** @var \Magento\Cms\Model\Block $block */ + $block = $this->_blockFactory->create(); + $block->setStoreId($storeId)->load($blockId); + $this->block = $block; + + return $block; + } catch (NoSuchEntityException $e) { + } + } + + return null; + } } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php index 3aaf40e7d0a..4af6b684496 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; -class Delete extends \Magento\Cms\Controller\Adminhtml\Block +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\Cms\Controller\Adminhtml\Block implements HttpPostActionInterface { /** * Delete action diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php index 87560890632..b5b035e0c67 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php @@ -5,7 +5,9 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; -class Edit extends \Magento\Cms\Controller\Adminhtml\Block +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php index a4096e4d1a4..f92d38c0d85 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; -class Index extends \Magento\Cms\Controller\Adminhtml\Block +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php index cfac00915c9..28db422fb51 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; -class NewAction extends \Magento\Cms\Controller\Adminhtml\Block +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** * @var \Magento\Backend\Model\View\Result\ForwardFactory diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php index 40974b7a4b5..7526f59ce36 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php @@ -6,6 +6,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Cms\Api\BlockRepositoryInterface; use Magento\Cms\Model\Block; @@ -14,7 +15,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; -class Save extends \Magento\Cms\Controller\Adminhtml\Block +class Save extends \Magento\Cms\Controller\Adminhtml\Block implements HttpPostActionInterface { /** * @var DataPersistorInterface diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php index 6d51c28b6ac..e7bdfe9ed0e 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php @@ -6,9 +6,10 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; -class Edit extends \Magento\Backend\App\Action +class Edit extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php index 75f9ad70dc4..42fa4d62673 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php @@ -6,10 +6,11 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class Index extends \Magento\Backend\App\Action +class Index extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php index a85b8ecd5e5..b07894e6a9d 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php @@ -5,6 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -13,7 +14,7 @@ /** * Class MassDisable */ -class MassDisable extends \Magento\Backend\App\Action +class MassDisable extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php index ef4fda60c0f..5a7b900bf2d 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php @@ -6,12 +6,13 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Cms\Model\Page; use Magento\Framework\App\Request\DataPersistorInterface; use Magento\Framework\Exception\LocalizedException; -class Save extends \Magento\Backend\App\Action +class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -127,8 +128,8 @@ public function execute() /** * Process result redirect * - * @param \Magento\Cms\Api\Data\PageInterface $model - * @param \Magento\Backend\Model\View\Result\Redirect $resultRedirect + * @param \Magento\Cms\Api\Data\PageInterface $model + * @param \Magento\Backend\Model\View\Result\Redirect $resultRedirect * @param array $data * @return \Magento\Backend\Model\View\Result\Redirect * @throws LocalizedException diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php index 1c46ac6807e..b268bee4c60 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php @@ -6,9 +6,11 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page\Widget; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; -class Chooser extends \Magento\Backend\App\Action +class Chooser extends Action implements HttpPostActionInterface, HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Index/Index.php b/app/code/Magento/Cms/Controller/Index/Index.php index c027bd1a2b7..59ed1a6248b 100644 --- a/app/code/Magento/Cms/Controller/Index/Index.php +++ b/app/code/Magento/Cms/Controller/Index/Index.php @@ -5,6 +5,8 @@ */ namespace Magento\Cms\Controller\Index; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; @@ -15,8 +17,12 @@ use Magento\Framework\View\Result\Page as ResultPage; use Magento\Cms\Helper\Page; use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Action\Action; -class Index extends \Magento\Framework\App\Action\Action +/** + * Home page. Needs to be accessible by POST because of the store switching. + */ +class Index extends Action implements HttpGetActionInterface, HttpPostActionInterface { /** * @var ForwardFactory diff --git a/app/code/Magento/Cms/Controller/Noroute/Index.php b/app/code/Magento/Cms/Controller/Noroute/Index.php index db84ce9556d..b30beae73dc 100644 --- a/app/code/Magento/Cms/Controller/Noroute/Index.php +++ b/app/code/Magento/Cms/Controller/Noroute/Index.php @@ -6,6 +6,9 @@ */ namespace Magento\Cms\Controller\Noroute; +/** + * @SuppressWarnings(PHPMD.AllPurposeAction) + */ class Index extends \Magento\Framework\App\Action\Action { /** diff --git a/app/code/Magento/Cms/Controller/Page/View.php b/app/code/Magento/Cms/Controller/Page/View.php index ab02bc5e717..9d5785450ec 100644 --- a/app/code/Magento/Cms/Controller/Page/View.php +++ b/app/code/Magento/Cms/Controller/Page/View.php @@ -6,7 +6,14 @@ */ namespace Magento\Cms\Controller\Page; -class View extends \Magento\Framework\App\Action\Action +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\Action; + +/** + * Custom page for storefront. Needs to be accessible by POST because of the store switching. + */ +class View extends Action implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\ForwardFactory diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index dfd55f5346e..b2ef78bab99 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -501,14 +501,6 @@ public function uploadFile($targetPath, $type = null) // create thumbnail $this->resizeFile($targetPath . '/' . $uploader->getUploadedFileName(), true); - $result['cookie'] = [ - 'name' => $this->getSession()->getName(), - 'value' => $this->getSession()->getSessionId(), - 'lifetime' => $this->getSession()->getCookieLifetime(), - 'path' => $this->getSession()->getCookiePath(), - 'domain' => $this->getSession()->getCookieDomain(), - ]; - return $result; } diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml index c89aa27c10e..691a99a73b9 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml @@ -17,8 +17,6 @@ <description value="Admin should be able to create a CMS page with widget type: Recently Compared Products"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-83792"/> - <!--Skip test due to MC-1284--> - <group value="skip"/> </annotations> <!--Main test--> <before> diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php index 2dc98bcefb9..309f08a54aa 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php @@ -440,14 +440,7 @@ public function testUploadFile() $thumbnailDestination = $thumbnailTargetPath . '/' . $fileName; $type = 'image'; $result = [ - 'result', - 'cookie' => [ - 'name' => 'session_name', - 'value' => '1', - 'lifetime' => '50', - 'path' => 'cookie/path', - 'domain' => 'cookie_domain', - ], + 'result' ]; $uploader = $this->getMockBuilder(\Magento\MediaStorage\Model\File\Uploader::class) ->disableOriginalConstructor() @@ -507,17 +500,6 @@ public function testUploadFile() $this->adapterFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($image); - $this->sessionMock->expects($this->atLeastOnce())->method('getName') - ->willReturn($result['cookie']['name']); - $this->sessionMock->expects($this->atLeastOnce())->method('getSessionId') - ->willReturn($result['cookie']['value']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookieLifetime') - ->willReturn($result['cookie']['lifetime']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookiePath') - ->willReturn($result['cookie']['path']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookieDomain') - ->willReturn($result['cookie']['domain']); - $this->assertEquals($result, $this->imagesStorage->uploadFile($targetPath, $type)); } } diff --git a/app/code/Magento/Cms/etc/db_schema.xml b/app/code/Magento/Cms/etc/db_schema.xml index 2b825544f56..3f97a94d549 100644 --- a/app/code/Magento/Cms/etc/db_schema.xml +++ b/app/code/Magento/Cms/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="cms_block" resource="default" engine="innodb" comment="CMS Block Table"> <column xsi:type="smallint" name="block_id" padding="6" unsigned="false" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="title" nullable="false" length="255" comment="Block Title"/> <column xsi:type="varchar" name="identifier" nullable="false" length="255" comment="Block String Identifier"/> <column xsi:type="mediumtext" name="content" nullable="true" comment="Block Content"/> @@ -46,7 +46,7 @@ </table> <table name="cms_page" resource="default" engine="innodb" comment="CMS Page Table"> <column xsi:type="smallint" name="page_id" padding="6" unsigned="false" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="title" nullable="true" length="255" comment="Page Title"/> <column xsi:type="varchar" name="page_layout" nullable="true" length="255" comment="Page Layout"/> <column xsi:type="text" name="meta_keywords" nullable="true" comment="Page Meta Keywords"/> @@ -86,7 +86,8 @@ </index> </table> <table name="cms_page_store" resource="default" engine="innodb" comment="CMS Page To Store Linkage Table"> - <column xsi:type="smallint" name="page_id" padding="6" unsigned="false" nullable="false" identity="false"/> + <column xsi:type="smallint" name="page_id" padding="6" unsigned="false" nullable="false" identity="false" + comment="Entity ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Store ID"/> <constraint xsi:type="primary" name="PRIMARY"> diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index eb515c91bbb..6a2e3950f93 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -8,6 +8,10 @@ "magento/module-cms": "*", "magento/module-widget": "*" }, + "suggest": { + "magento/module-graph-ql": "*", + "magento/module-store-graph-ql": "*" + }, "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CmsGraphQl/etc/graphql/di.xml b/app/code/Magento/CmsGraphQl/etc/graphql/di.xml new file mode 100644 index 00000000000..78c1071d8e0 --- /dev/null +++ b/app/code/Magento/CmsGraphQl/etc/graphql/di.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider"> + <arguments> + <argument name="extendedConfigData" xsi:type="array"> + <item name="front" xsi:type="string">web/default/front</item> + <item name="cms_home_page" xsi:type="string">web/default/cms_home_page</item> + <item name="no_route" xsi:type="string">web/default/no_route</item> + <item name="cms_no_route" xsi:type="string">web/default/cms_no_route</item> + <item name="cms_no_cookies" xsi:type="string">web/default/cms_no_cookies</item> + <item name="show_cms_breadcrumbs" xsi:type="string">web/default/show_cms_breadcrumbs</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/CmsGraphQl/etc/schema.graphqls b/app/code/Magento/CmsGraphQl/etc/schema.graphqls index 997bbf920a0..e8abd2201b8 100644 --- a/app/code/Magento/CmsGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CmsGraphQl/etc/schema.graphqls @@ -1,5 +1,14 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type StoreConfig @doc(description: "The type contains information about a store config") { + front : String @doc(description: "Default Web URL") + cms_home_page : String @doc(description: "CMS Home Page") + no_route : String @doc(description: "Default No-route URL") + cms_no_route : String @doc(description: "CMS No Route Page") + cms_no_cookies : String @doc(description: "CMS No Cookies Page") + show_cms_breadcrumbs : Int @doc(description: "Show Breadcrumbs for CMS Pages") +} + type Query { cmsPage ( diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php index 479b4f5c217..f5d109b198d 100644 --- a/app/code/Magento/Config/App/Config/Type/System.php +++ b/app/code/Magento/Config/App/Config/Type/System.php @@ -5,58 +5,46 @@ */ namespace Magento\Config\App\Config\Type; +use Magento\Framework\App\Config\ConfigSourceInterface; use Magento\Framework\App\Config\ConfigTypeInterface; +use Magento\Framework\App\Config\Spi\PostProcessorInterface; +use Magento\Framework\App\Config\Spi\PreProcessorInterface; use Magento\Framework\App\ObjectManager; use Magento\Config\App\Config\Type\System\Reader; +use Magento\Framework\App\ScopeInterface; +use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Store\Model\Config\Processor\Fallback; +use Magento\Store\Model\ScopeInterface as StoreScope; /** * System configuration type + * * @api * @since 100.1.2 */ class System implements ConfigTypeInterface { const CACHE_TAG = 'config_scopes'; - const CONFIG_TYPE = 'system'; - /** - * @var \Magento\Framework\App\Config\ConfigSourceInterface - */ - private $source; - /** * @var array */ private $data = []; /** - * @var \Magento\Framework\App\Config\Spi\PostProcessorInterface + * @var PostProcessorInterface */ private $postProcessor; /** - * @var \Magento\Framework\App\Config\Spi\PreProcessorInterface - */ - private $preProcessor; - - /** - * @var \Magento\Framework\Cache\FrontendInterface + * @var FrontendInterface */ private $cache; /** - * @var int - */ - private $cachingNestedLevel; - - /** - * @var \Magento\Store\Model\Config\Processor\Fallback - */ - private $fallback; - - /** - * @var \Magento\Framework\Serialize\SerializerInterface + * @var SerializerInterface */ private $serializer; @@ -79,36 +67,34 @@ class System implements ConfigTypeInterface * * @var array */ - private $availableDataScopes = null; + private $availableDataScopes; /** - * @param \Magento\Framework\App\Config\ConfigSourceInterface $source - * @param \Magento\Framework\App\Config\Spi\PostProcessorInterface $postProcessor - * @param \Magento\Store\Model\Config\Processor\Fallback $fallback - * @param \Magento\Framework\Cache\FrontendInterface $cache - * @param \Magento\Framework\Serialize\SerializerInterface $serializer - * @param \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor + * @param ConfigSourceInterface $source + * @param PostProcessorInterface $postProcessor + * @param Fallback $fallback + * @param FrontendInterface $cache + * @param SerializerInterface $serializer + * @param PreProcessorInterface $preProcessor * @param int $cachingNestedLevel * @param string $configType * @param Reader $reader + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( - \Magento\Framework\App\Config\ConfigSourceInterface $source, - \Magento\Framework\App\Config\Spi\PostProcessorInterface $postProcessor, - \Magento\Store\Model\Config\Processor\Fallback $fallback, - \Magento\Framework\Cache\FrontendInterface $cache, - \Magento\Framework\Serialize\SerializerInterface $serializer, - \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor, + ConfigSourceInterface $source, + PostProcessorInterface $postProcessor, + Fallback $fallback, + FrontendInterface $cache, + SerializerInterface $serializer, + PreProcessorInterface $preProcessor, $cachingNestedLevel = 1, $configType = self::CONFIG_TYPE, Reader $reader = null ) { - $this->source = $source; $this->postProcessor = $postProcessor; - $this->preProcessor = $preProcessor; $this->cache = $cache; - $this->cachingNestedLevel = $cachingNestedLevel; - $this->fallback = $fallback; $this->serializer = $serializer; $this->configType = $configType; $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class); @@ -136,27 +122,52 @@ public function get($path = '') { if ($path === '') { $this->data = array_replace_recursive($this->loadAllData(), $this->data); + return $this->data; } + + return $this->getWithParts($path); + } + + /** + * Proceed with parts extraction from path. + * + * @param string $path + * @return array|int|string|boolean + */ + private function getWithParts($path) + { $pathParts = explode('/', $path); - if (count($pathParts) === 1 && $pathParts[0] !== 'default') { + + if (count($pathParts) === 1 && $pathParts[0] !== ScopeInterface::SCOPE_DEFAULT) { if (!isset($this->data[$pathParts[0]])) { $data = $this->readData(); $this->data = array_replace_recursive($data, $this->data); } + return $this->data[$pathParts[0]]; } + $scopeType = array_shift($pathParts); - if ($scopeType === 'default') { + + if ($scopeType === ScopeInterface::SCOPE_DEFAULT) { if (!isset($this->data[$scopeType])) { $this->data = array_replace_recursive($this->loadDefaultScopeData($scopeType), $this->data); } + return $this->getDataByPathParts($this->data[$scopeType], $pathParts); } + $scopeId = array_shift($pathParts); + if (!isset($this->data[$scopeType][$scopeId])) { - $this->data = array_replace_recursive($this->loadScopeData($scopeType, $scopeId), $this->data); + $scopeData = $this->loadScopeData($scopeType, $scopeId); + + if (!isset($this->data[$scopeType][$scopeId])) { + $this->data = array_replace_recursive($scopeData, $this->data); + } } + return isset($this->data[$scopeType][$scopeId]) ? $this->getDataByPathParts($this->data[$scopeType][$scopeId], $pathParts) : null; @@ -170,11 +181,13 @@ public function get($path = '') private function loadAllData() { $cachedData = $this->cache->load($this->configType); + if ($cachedData === false) { $data = $this->readData(); } else { $data = $this->serializer->unserialize($cachedData); } + return $data; } @@ -187,12 +200,14 @@ private function loadAllData() private function loadDefaultScopeData($scopeType) { $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + if ($cachedData === false) { $data = $this->readData(); $this->cacheData($data); } else { $data = [$scopeType => $this->serializer->unserialize($cachedData)]; } + return $data; } @@ -206,6 +221,7 @@ private function loadDefaultScopeData($scopeType) private function loadScopeData($scopeType, $scopeId) { $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + if ($cachedData === false) { if ($this->availableDataScopes === null) { $cachedScopeData = $this->cache->load($this->configType . '_scopes'); @@ -221,6 +237,7 @@ private function loadScopeData($scopeType, $scopeId) } else { $data = [$scopeType => [$scopeId => $this->serializer->unserialize($cachedData)]]; } + return $data; } @@ -244,7 +261,7 @@ private function cacheData(array $data) [self::CACHE_TAG] ); $scopes = []; - foreach (['websites', 'stores'] as $curScopeType) { + foreach ([StoreScope::SCOPE_WEBSITES, StoreScope::SCOPE_STORES] as $curScopeType) { foreach ($data[$curScopeType] ?? [] as $curScopeId => $curScopeData) { $scopes[$curScopeType][$curScopeId] = 1; $this->cache->save( @@ -256,7 +273,7 @@ private function cacheData(array $data) } $this->cache->save( $this->serializer->serialize($scopes), - $this->configType . "_scopes", + $this->configType . '_scopes', [self::CACHE_TAG] ); } @@ -279,6 +296,7 @@ private function getDataByPathParts($data, $pathParts) return null; } } + return $data; } diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php b/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php index 63dbb2b80e3..16b18e02000 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php @@ -33,6 +33,8 @@ public function __construct( } /** + * Returns element html + * * @param AbstractElement $element * @return string * @codeCoverageIgnore @@ -40,7 +42,7 @@ public function __construct( protected function _getElementHtml(AbstractElement $element) { return $this->dateTimeFormatter->formatObject( - $this->_localeDate->date(intval($element->getValue())), + $this->_localeDate->date((int) $element->getValue()), $this->_localeDate->getDateTimeFormat(\IntlDateFormatter::MEDIUM) ); } diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php b/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php index 7f21bf4b92b..2e79cec7088 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php @@ -10,6 +10,7 @@ /** * Backend system config datetime field renderer + * * @api * @since 100.0.2 */ @@ -35,6 +36,8 @@ public function __construct( } /** + * Returns element html + * * @param AbstractElement $element * @return string */ @@ -44,6 +47,6 @@ protected function _getElementHtml(AbstractElement $element) $format = $this->_localeDate->getDateTimeFormat( \IntlDateFormatter::MEDIUM ); - return $this->dateTimeFormatter->formatObject($this->_localeDate->date(intval($element->getValue())), $format); + return $this->dateTimeFormatter->formatObject($this->_localeDate->date((int) $element->getValue()), $format); } } diff --git a/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php b/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php index 582f8750808..aeb57010e49 100644 --- a/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php @@ -97,7 +97,7 @@ public function process($scope, $scopeCode, $value, $path) $field = $configStructure->getElementByConfigPath($path); /** @var Value $backendModel */ - $backendModel = $field && $field->hasBackendModel() + $backendModel = $field instanceof Field && $field->hasBackendModel() ? $field->getBackendModel() : $this->configValueFactory->create(); diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Edit.php b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Edit.php index b65f6e9d4e4..12c6ecbab9f 100644 --- a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Edit.php +++ b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Config\Controller\Adminhtml\System\Config; -class Edit extends AbstractScopeConfig +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends AbstractScopeConfig implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Index.php b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Index.php index 66290a79261..03479085f3f 100644 --- a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Index.php +++ b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Config\Controller\Adminhtml\System\Config; -class Index extends AbstractScopeConfig +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends AbstractScopeConfig implements HttpGetActionInterface { /** * @var \Magento\Backend\Model\View\Result\ForwardFactory diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php index 290a43c9cd6..2d4b2003380 100644 --- a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php +++ b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php @@ -5,6 +5,7 @@ */ namespace Magento\Config\Controller\Adminhtml\System\Config; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Config\Controller\Adminhtml\System\AbstractConfig; /** @@ -13,7 +14,7 @@ * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends AbstractConfig +class Save extends AbstractConfig implements HttpPostActionInterface { /** * Backend Config Model Factory diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/Config/State.php b/app/code/Magento/Config/Controller/Adminhtml/System/Config/State.php index 75716fa380b..5d74eb7ea4e 100644 --- a/app/code/Magento/Config/Controller/Adminhtml/System/Config/State.php +++ b/app/code/Magento/Config/Controller/Adminhtml/System/Config/State.php @@ -6,7 +6,13 @@ */ namespace Magento\Config\Controller\Adminhtml\System\Config; -class State extends AbstractScopeConfig +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Save current state of open tabs. GET is allowed for legacy reasons. + */ +class State extends AbstractScopeConfig implements HttpPostActionInterface, HttpGetActionInterface { /** * @var \Magento\Framework\Controller\Result\RawFactory 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 2733847bab1..19e1acc6170 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php +++ b/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php @@ -10,6 +10,8 @@ namespace Magento\Config\Model\Config\Structure\Mapper; /** + * Sorting mapper + * * @api * @since 100.0.2 */ @@ -30,6 +32,8 @@ public function map(array $data) } /** + * Process config + * * @param array $data * @return array */ @@ -62,10 +66,6 @@ protected function _cmp($elementA, $elementB) $sortIndexB = (float)$elementB['sortOrder']; } - if ($sortIndexA == $sortIndexB) { - return 0; - } - - return $sortIndexA < $sortIndexB ? -1 : 1; + return $sortIndexA <=> $sortIndexB; } } diff --git a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml index 85035c6b56f..88532dc091f 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml @@ -16,7 +16,7 @@ <element name="SwitcherSystemValue" type="button" selector="#cms_wysiwyg_editor_inherit"/> <element name="Switcher" type="button" selector="#cms_wysiwyg_editor" /> <element name="StaticURL" type="button" selector="#cms_wysiwyg_use_static_urls_in_catalog" /> - <element name="Save" type="button" selector="#save"/> + <element name="Save" type="button" selector="#save" timeout="30"/> </section> <section name="WebSection"> <element name="DefaultLayoutsTab" type="button" selector="#web_default_layouts-head"/> diff --git a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Button/Save.php b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Button/Save.php index 8848fc78dad..f2de5e72211 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Button/Save.php +++ b/app/code/Magento/ConfigurableProduct/Block/Adminhtml/Product/Edit/Button/Save.php @@ -8,7 +8,6 @@ use Magento\Ui\Component\Control\Container; use Magento\Catalog\Block\Adminhtml\Product\Edit\Button\Generic; use Magento\ConfigurableProduct\Model\Product\Type\Configurable as ConfigurableType; -use Magento\Catalog\Model\Product\Type; /** * Class Save @@ -16,16 +15,7 @@ class Save extends Generic { /** - * @var array - */ - private static $availableProductTypes = [ - ConfigurableType::TYPE_CODE, - Type::TYPE_SIMPLE, - Type::TYPE_VIRTUAL - ]; - - /** - * {@inheritdoc} + * @inheritdoc */ public function getButtonData() { @@ -135,7 +125,8 @@ protected function getOptions() } /** - * Retrieve target for button + * Retrieve target for button. + * * @return string */ protected function getSaveTarget() @@ -148,7 +139,8 @@ protected function getSaveTarget() } /** - * Retrieve action for button + * Retrieve action for button. + * * @return string */ protected function getSaveAction() @@ -161,10 +153,12 @@ protected function getSaveAction() } /** + * Is configurable product. + * * @return boolean */ protected function isConfigurableProduct() { - return in_array($this->getProduct()->getTypeId(), self::$availableProductTypes); + return !$this->getProduct()->isComposite() || $this->getProduct()->getTypeId() === ConfigurableType::TYPE_CODE; } } diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/AddAttribute.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/AddAttribute.php index 5991097e456..34f10b59f3a 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/AddAttribute.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/AddAttribute.php @@ -6,10 +6,11 @@ */ namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Framework\Controller\ResultFactory; -class AddAttribute extends Action +class AddAttribute extends Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php index 6f5f106a8bb..cfa2562d974 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/CreateOptions.php @@ -6,10 +6,11 @@ */ namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Attribute; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory; -class CreateOptions extends Action +class CreateOptions extends Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/GetAttributes.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/GetAttributes.php index b6b34073db6..9f5d5062b53 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/GetAttributes.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Attribute/GetAttributes.php @@ -6,10 +6,11 @@ */ namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Attribute; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\ConfigurableProduct\Model\AttributesListInterface; -class GetAttributes extends Action +class GetAttributes extends Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Wizard.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Wizard.php index 8adfdea9610..104181aed4f 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Wizard.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Wizard.php @@ -5,6 +5,8 @@ */ namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Framework\Controller\ResultFactory; use Magento\Catalog\Controller\Adminhtml\Product\Builder; @@ -13,7 +15,7 @@ /** * Class Wizard */ -class Wizard extends Action +class Wizard extends Action implements HttpPostActionInterface, HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml new file mode 100644 index 00000000000..033e6757c3b --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateApiConfigurableProductActionGroup"> + <arguments> + <argument name="productName" defaultValue="ApiConfigurableProductWithOutCategory" type="string"/> + </arguments> + + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="ApiConfigurableProductWithOutCategory" stepKey="createConfigProduct"> + <field key="name">{{productName}}</field> + </createData> + + <!-- Create attribute with 2 options to be used in children products --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="addAttributeToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml index 2d42e12912c..e7d9d61491a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml @@ -36,6 +36,17 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiConfigurableProductWithOutCategory" type="product"> + <data key="sku" unique="suffix">api-configurable-product-with-out-category</data> + <data key="type_id">configurable</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">API Configurable Product</data> + <data key="urlKey" unique="suffix">api-configurable-product</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + </entity> <entity name="ApiConfigurableProductWithDescription" type="product"> <data key="sku" unique="suffix">api-configurable-product</data> <data key="type_id">configurable</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Button/SaveTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Button/SaveTest.php index 8df6df53cc0..2d73b61245a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Button/SaveTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Adminhtml/Product/Edit/Button/SaveTest.php @@ -38,7 +38,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->productMock = $this->getMockBuilder(ProductInterface::class) - ->setMethods(['isReadonly', 'isDuplicable']) + ->setMethods(['isReadonly', 'isDuplicable', 'isComposite']) ->getMockForAbstractClass(); $this->registryMock->expects(static::any()) 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 cc565cab6b2..1fa65e03f54 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 @@ -233,7 +233,7 @@ define([ */ requestAttributes: function (attributeIds) { $.ajax({ - type: 'POST', + type: 'GET', url: this.optionsUrl, data: { attributes: attributeIds diff --git a/app/code/Magento/Contact/Controller/Index/Index.php b/app/code/Magento/Contact/Controller/Index/Index.php index 4b734c4f9b6..562b0770872 100644 --- a/app/code/Magento/Contact/Controller/Index/Index.php +++ b/app/code/Magento/Contact/Controller/Index/Index.php @@ -6,9 +6,10 @@ */ namespace Magento\Contact\Controller\Index; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; -class Index extends \Magento\Contact\Controller\Index +class Index extends \Magento\Contact\Controller\Index implements HttpGetActionInterface { /** * Show Contact Us page diff --git a/app/code/Magento/Contact/Controller/Index/Post.php b/app/code/Magento/Contact/Controller/Index/Post.php index bdc2fee088d..cbe49a76726 100644 --- a/app/code/Magento/Contact/Controller/Index/Post.php +++ b/app/code/Magento/Contact/Controller/Index/Post.php @@ -7,6 +7,7 @@ namespace Magento\Contact\Controller\Index; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Contact\Model\ConfigInterface; use Magento\Contact\Model\MailInterface; use Magento\Framework\App\Action\Context; @@ -17,7 +18,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; -class Post extends \Magento\Contact\Controller\Index +class Post extends \Magento\Contact\Controller\Index implements HttpPostActionInterface { /** * @var DataPersistorInterface diff --git a/app/code/Magento/Cron/Test/Unit/Model/System/Config/Initial/ConverterTest.php b/app/code/Magento/Cron/Test/Unit/Model/System/Config/Initial/ConverterTest.php new file mode 100644 index 00000000000..703926b4c01 --- /dev/null +++ b/app/code/Magento/Cron/Test/Unit/Model/System/Config/Initial/ConverterTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cron\Test\Unit\Model\System\Config\Initial; + +use Magento\Cron\Model\Groups\Config\Data as GroupsConfigModel; +use Magento\Cron\Model\System\Config\Initial\Converter as ConverterPlugin; +use Magento\Framework\App\Config\Initial\Converter; + +/** + * Class ConverterTest + * + * Unit test for \Magento\Cron\Model\System\Config\Initial\Converter + */ +class ConverterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var GroupsConfigModel|\PHPUnit_Framework_MockObject_MockObject + */ + private $groupsConfigMock; + + /** + * @var Converter|\PHPUnit_Framework_MockObject_MockObject + */ + private $converterMock; + + /** + * @var ConverterPlugin + */ + private $converterPlugin; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->groupsConfigMock = $this->getMockBuilder( + GroupsConfigModel::class + )->disableOriginalConstructor()->getMock(); + $this->converterMock = $this->getMockBuilder(Converter::class)->getMock(); + $this->converterPlugin = new ConverterPlugin($this->groupsConfigMock); + } + + /** + * Tests afterConvert method with no $result['data']['default']['system'] set + */ + public function testAfterConvertWithNoData() + { + $expectedResult = ['test']; + $this->groupsConfigMock->expects($this->never()) + ->method('get'); + + $result = $this->converterPlugin->afterConvert($this->converterMock, $expectedResult); + + self::assertSame($expectedResult, $result); + } + + /** + * Tests afterConvert method with $result['data']['default']['system'] set + */ + public function testAfterConvertWithData() + { + $groups = [ + 'group1' => ['val1' => ['value' => '1']], + 'group2' => ['val2' => ['value' => '2']] + ]; + $expectedResult['data']['default']['system']['cron'] = [ + 'group1' => [ + 'val1' => '1' + ], + 'group2' => [ + 'val2' => '2' + ] + ]; + $result['data']['default']['system']['cron'] = '1'; + + $this->groupsConfigMock->expects($this->once()) + ->method('get') + ->willReturn($groups); + + $result = $this->converterPlugin->afterConvert($this->converterMock, $result); + + self::assertEquals($expectedResult, $result); + } +} diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml index a37f3760b70..3e3bdc20535 100644 --- a/app/code/Magento/Cron/etc/di.xml +++ b/app/code/Magento/Cron/etc/di.xml @@ -16,6 +16,18 @@ <type name="Magento\Framework\App\Config\Initial\Converter"> <plugin name="cron_system_config_initial_converter_plugin" type="Magento\Cron\Model\System\Config\Initial\Converter" /> </type> + <virtualType name="Magento\Cron\Model\VirtualLoggerHandler" type="Magento\Framework\Logger\Handler\Base"> + <arguments> + <argument name="fileName" xsi:type="string">/var/log/cron.log</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Cron\Model\VirtualLogger" type="Magento\Framework\Logger\Monolog"> + <arguments> + <argument name="handlers" xsi:type="array"> + <item name="system" xsi:type="object">Magento\Cron\Model\VirtualLoggerHandler</item> + </argument> + </arguments> + </virtualType> <!-- @api --> <virtualType name="shellBackground" type="Magento\Framework\Shell"> <arguments> @@ -25,6 +37,7 @@ <type name="Magento\Cron\Observer\ProcessCronQueueObserver"> <arguments> <argument name="shell" xsi:type="object">shellBackground</argument> + <argument name="logger" xsi:type="object">Magento\Cron\Model\VirtualLogger</argument> </arguments> </type> <type name="Magento\Framework\Console\CommandListInterface"> diff --git a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRates.php b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRates.php index 38e20355b66..34d24a8b0a7 100644 --- a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRates.php +++ b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRates.php @@ -7,10 +7,13 @@ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currency; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Controller\ResultFactory; +use Magento\CurrencySymbol\Controller\Adminhtml\System\Currency as CurrencyAction; -class FetchRates extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currency +class FetchRates extends CurrencyAction implements HttpGetActionInterface, HttpPostActionInterface { /** * Fetch rates action diff --git a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/Index.php b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/Index.php index 9b07777a14d..a9b1b78cbc6 100644 --- a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/Index.php +++ b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currency; -class Index extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currency +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currency implements HttpGetActionInterface { /** * Currency management main page diff --git a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRates.php b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRates.php index ae13c4d399e..8dd6b5e6fac 100644 --- a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRates.php +++ b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRates.php @@ -7,7 +7,9 @@ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currency; -class SaveRates extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currency +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class SaveRates extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currency implements HttpPostActionInterface { /** * Save rates action diff --git a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Index.php b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Index.php index 808372bd3a6..1762a907a75 100644 --- a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Index.php +++ b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol; -class Index extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol implements HttpGetActionInterface { /** * Show Currency Symbols Management dialog diff --git a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php index eee7961b02f..703117f34fc 100644 --- a/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php +++ b/app/code/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/Save.php @@ -6,7 +6,9 @@ */ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol; -class Save extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Save extends \Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol implements HttpPostActionInterface { /** * Save custom Currency symbol diff --git a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php index fcde688a1e1..6c7019986cc 100644 --- a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php +++ b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php @@ -187,14 +187,16 @@ public function getCurrencySymbolsData() /** * Save currency symbol to config * - * @param $symbols array + * @param array $symbols * @return $this */ public function setCurrencySymbolsData($symbols = []) { - foreach ($this->getCurrencySymbolsData() as $code => $values) { - if (isset($symbols[$code]) && ($symbols[$code] == $values['parentSymbol'] || empty($symbols[$code]))) { - unset($symbols[$code]); + if (!$this->_storeManager->isSingleStoreMode()) { + foreach ($this->getCurrencySymbolsData() as $code => $values) { + if (isset($symbols[$code]) && ($symbols[$code] == $values['parentSymbol'] || empty($symbols[$code]))) { + unset($symbols[$code]); + } } } $value = []; diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml index 0ba3c7ed2d7..6e9b9a396ec 100644 --- a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml +++ b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml @@ -23,10 +23,9 @@ </label> <div class="admin__field-control"> <input id="custom_currency_symbol<?= /* @escapeNotVerified */ $code ?>" - class="required-entry admin__control-text" + class="required-entry admin__control-text <?= $data['inherited'] ? 'disabled' : '' ?>" type="text" value="<?= $block->escapeHtmlAttr($data['displaySymbol']) ?>" - <?= $data['inherited'] ? ' disabled="disabled"' : '' ?> name="custom_currency_symbol[<?= /* @escapeNotVerified */ $code ?>]"> <div class="admin__field admin__field-option"> <input id="custom_currency_symbol_inherit<?= /* @escapeNotVerified */ $code ?>" @@ -49,16 +48,18 @@ require(['jquery', "mage/mage", 'prototype'], function(jQuery){ function toggleUseDefault(code, value) { - checkbox = $('custom_currency_symbol_inherit'+code); - input = $('custom_currency_symbol'+code); - if (checkbox.checked) { - input.value = value; - input.disabled = true; + checkbox = jQuery('#custom_currency_symbol_inherit'+code); + input = jQuery('#custom_currency_symbol'+code); + + if (checkbox.is(':checked')) { + input.addClass('disabled'); + input.val(value); + input.prop('readonly', true); } else { - input.disabled = false; + input.removeClass('disabled'); + input.prop('readonly', false); } } - window.toggleUseDefault = toggleUseDefault; }); </script> diff --git a/app/code/Magento/Customer/Controller/Account/Create.php b/app/code/Magento/Customer/Controller/Account/Create.php index 8f1bc107547..450fe461534 100644 --- a/app/code/Magento/Customer/Controller/Account/Create.php +++ b/app/code/Magento/Customer/Controller/Account/Create.php @@ -6,12 +6,13 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\Model\Registration; use Magento\Customer\Model\Session; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\App\Action\Context; -class Create extends \Magento\Customer\Controller\AbstractAccount +class Create extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface { /** * @var \Magento\Customer\Model\Registration diff --git a/app/code/Magento/Customer/Controller/Account/CreatePost.php b/app/code/Magento/Customer/Controller/Account/CreatePost.php index bb94063226f..79a575add73 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePost.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePost.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Model\Account\Redirect as AccountRedirect; use Magento\Customer\Api\Data\AddressInterface; use Magento\Framework\Api\DataObjectHelper; @@ -40,7 +41,7 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CreatePost extends AbstractAccount implements CsrfAwareActionInterface +class CreatePost extends AbstractAccount implements CsrfAwareActionInterface, HttpPostActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface diff --git a/app/code/Magento/Customer/Controller/Account/Edit.php b/app/code/Magento/Customer/Controller/Account/Edit.php index 3c1e6019939..7c2b7215a05 100644 --- a/app/code/Magento/Customer/Controller/Account/Edit.php +++ b/app/code/Magento/Customer/Controller/Account/Edit.php @@ -6,13 +6,14 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Framework\Api\DataObjectHelper; use Magento\Customer\Model\Session; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\App\Action\Context; -class Edit extends \Magento\Customer\Controller\AbstractAccount +class Edit extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface { /** * @var \Magento\Customer\Api\CustomerRepositoryInterface diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php index aa5e088f9c8..e3b3d834522 100644 --- a/app/code/Magento/Customer/Controller/Account/EditPost.php +++ b/app/code/Magento/Customer/Controller/Account/EditPost.php @@ -7,6 +7,7 @@ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Model\AuthenticationInterface; use Magento\Customer\Model\Customer\Mapper; use Magento\Customer\Model\EmailNotificationInterface; @@ -31,7 +32,7 @@ * Class EditPost * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class EditPost extends AbstractAccount implements CsrfAwareActionInterface +class EditPost extends AbstractAccount implements CsrfAwareActionInterface, HttpPostActionInterface { /** * Form code for data extractor diff --git a/app/code/Magento/Customer/Controller/Account/ForgotPassword.php b/app/code/Magento/Customer/Controller/Account/ForgotPassword.php index f115b64efeb..8b5d0612050 100644 --- a/app/code/Magento/Customer/Controller/Account/ForgotPassword.php +++ b/app/code/Magento/Customer/Controller/Account/ForgotPassword.php @@ -6,11 +6,12 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class ForgotPassword extends \Magento\Customer\Controller\AbstractAccount +class ForgotPassword extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface { /** * @var PageFactory diff --git a/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php b/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php index f3024738730..3c7fca99184 100644 --- a/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php +++ b/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php @@ -6,6 +6,7 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Model\AccountManagement; use Magento\Customer\Model\Session; @@ -18,7 +19,7 @@ * ForgotPasswordPost controller * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class ForgotPasswordPost extends \Magento\Customer\Controller\AbstractAccount +class ForgotPasswordPost extends \Magento\Customer\Controller\AbstractAccount implements HttpPostActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface diff --git a/app/code/Magento/Customer/Controller/Account/Index.php b/app/code/Magento/Customer/Controller/Account/Index.php index 2ecf79d35b1..301fd584cfa 100644 --- a/app/code/Magento/Customer/Controller/Account/Index.php +++ b/app/code/Magento/Customer/Controller/Account/Index.php @@ -6,10 +6,11 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class Index extends \Magento\Customer\Controller\AbstractAccount +class Index extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface { /** * @var PageFactory diff --git a/app/code/Magento/Customer/Controller/Account/Login.php b/app/code/Magento/Customer/Controller/Account/Login.php index d685191bf43..64cc24095f9 100644 --- a/app/code/Magento/Customer/Controller/Account/Login.php +++ b/app/code/Magento/Customer/Controller/Account/Login.php @@ -6,12 +6,17 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\View\Result\PageFactory; use Magento\Customer\Controller\AbstractAccount; -class Login extends AbstractAccount +/** + * Login form page. Accepts POST for backward compatibility reasons. + */ +class Login extends AbstractAccount implements HttpGetActionInterface, HttpPostActionInterface { /** * @var Session diff --git a/app/code/Magento/Customer/Controller/Account/LoginPost.php b/app/code/Magento/Customer/Controller/Account/LoginPost.php index 5c7eee78e5f..04051fbbf36 100644 --- a/app/code/Magento/Customer/Controller/Account/LoginPost.php +++ b/app/code/Magento/Customer/Controller/Account/LoginPost.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Model\Account\Redirect as AccountRedirect; use Magento\Framework\App\Action\Context; use Magento\Customer\Model\Session; @@ -27,7 +28,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class LoginPost extends AbstractAccount implements CsrfAwareActionInterface +class LoginPost extends AbstractAccount implements CsrfAwareActionInterface, HttpPostActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface diff --git a/app/code/Magento/Customer/Controller/Account/Logout.php b/app/code/Magento/Customer/Controller/Account/Logout.php index 19dabf9effa..9344f482bd6 100644 --- a/app/code/Magento/Customer/Controller/Account/Logout.php +++ b/app/code/Magento/Customer/Controller/Account/Logout.php @@ -6,6 +6,8 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\Context; use Magento\Framework\App\ObjectManager; @@ -13,7 +15,10 @@ use Magento\Framework\Stdlib\Cookie\PhpCookieManager; use Magento\Customer\Controller\AbstractAccount; -class Logout extends AbstractAccount +/** + * Sign out a customer. + */ +class Logout extends AbstractAccount implements HttpGetActionInterface, HttpPostActionInterface { /** * @var Session diff --git a/app/code/Magento/Customer/Controller/Account/LogoutSuccess.php b/app/code/Magento/Customer/Controller/Account/LogoutSuccess.php index c58416434c0..e00494d221d 100644 --- a/app/code/Magento/Customer/Controller/Account/LogoutSuccess.php +++ b/app/code/Magento/Customer/Controller/Account/LogoutSuccess.php @@ -6,10 +6,11 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class LogoutSuccess extends \Magento\Customer\Controller\AbstractAccount +class LogoutSuccess extends \Magento\Customer\Controller\AbstractAccount implements HttpGetActionInterface { /** * @var PageFactory diff --git a/app/code/Magento/Customer/Controller/Address/Delete.php b/app/code/Magento/Customer/Controller/Address/Delete.php index ef92bd2ef53..a4a0944137e 100644 --- a/app/code/Magento/Customer/Controller/Address/Delete.php +++ b/app/code/Magento/Customer/Controller/Address/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\Customer\Controller\Address; -class Delete extends \Magento\Customer\Controller\Address +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\Customer\Controller\Address implements HttpPostActionInterface { /** * @return \Magento\Framework\Controller\Result\Redirect diff --git a/app/code/Magento/Customer/Controller/Address/Edit.php b/app/code/Magento/Customer/Controller/Address/Edit.php index a30eb10b115..0a5affefae3 100644 --- a/app/code/Magento/Customer/Controller/Address/Edit.php +++ b/app/code/Magento/Customer/Controller/Address/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Customer\Controller\Address; -class Edit extends \Magento\Customer\Controller\Address +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Customer\Controller\Address implements HttpGetActionInterface { /** * Customer address edit action diff --git a/app/code/Magento/Customer/Controller/Address/Form.php b/app/code/Magento/Customer/Controller/Address/Form.php index fc62a6c1a57..9b3f4e36be0 100644 --- a/app/code/Magento/Customer/Controller/Address/Form.php +++ b/app/code/Magento/Customer/Controller/Address/Form.php @@ -6,7 +6,9 @@ */ namespace Magento\Customer\Controller\Address; -class Form extends \Magento\Customer\Controller\Address +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Form extends \Magento\Customer\Controller\Address implements HttpGetActionInterface { /** * Address book form diff --git a/app/code/Magento/Customer/Controller/Address/FormPost.php b/app/code/Magento/Customer/Controller/Address/FormPost.php index 60e1f6eb172..217af0abd75 100644 --- a/app/code/Magento/Customer/Controller/Address/FormPost.php +++ b/app/code/Magento/Customer/Controller/Address/FormPost.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Address; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\AddressRepositoryInterface; use Magento\Customer\Api\Data\AddressInterfaceFactory; use Magento\Customer\Api\Data\RegionInterface; @@ -27,7 +28,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class FormPost extends \Magento\Customer\Controller\Address +class FormPost extends \Magento\Customer\Controller\Address implements HttpPostActionInterface { /** * @var RegionFactory diff --git a/app/code/Magento/Customer/Controller/Address/Index.php b/app/code/Magento/Customer/Controller/Address/Index.php index ad04c7bd5c7..92c6078349d 100644 --- a/app/code/Magento/Customer/Controller/Address/Index.php +++ b/app/code/Magento/Customer/Controller/Address/Index.php @@ -6,12 +6,13 @@ */ namespace Magento\Customer\Controller\Address; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\Api\CustomerRepositoryInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Index extends \Magento\Customer\Controller\Address +class Index extends \Magento\Customer\Controller\Address implements HttpGetActionInterface { /** * @var CustomerRepositoryInterface @@ -28,9 +29,9 @@ class Index extends \Magento\Customer\Controller\Address * @param \Magento\Customer\Api\Data\RegionInterfaceFactory $regionDataFactory * @param \Magento\Framework\Reflection\DataObjectProcessor $dataProcessor * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper - * @param CustomerRepositoryInterface $customerRepository * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param CustomerRepositoryInterface $customerRepository * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( diff --git a/app/code/Magento/Customer/Controller/Address/NewAction.php b/app/code/Magento/Customer/Controller/Address/NewAction.php index e97c7460578..043c2b91db2 100644 --- a/app/code/Magento/Customer/Controller/Address/NewAction.php +++ b/app/code/Magento/Customer/Controller/Address/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Customer\Controller\Address; -class NewAction extends \Magento\Customer\Controller\Address +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Customer\Controller\Address implements HttpGetActionInterface { /** * @return \Magento\Framework\Controller\Result\Forward diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Group/Delete.php b/app/code/Magento/Customer/Controller/Adminhtml/Group/Delete.php index 571ef57702b..ab32ea08a44 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Group/Delete.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Group/Delete.php @@ -6,9 +6,10 @@ */ namespace Magento\Customer\Controller\Adminhtml\Group; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; -class Delete extends \Magento\Customer\Controller\Adminhtml\Group +class Delete extends \Magento\Customer\Controller\Adminhtml\Group implements HttpPostActionInterface { /** * Delete customer group. diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Group/Edit.php b/app/code/Magento/Customer/Controller/Adminhtml/Group/Edit.php index 9da132078c9..221d01a112e 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Group/Edit.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Group/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Customer\Controller\Adminhtml\Group; -class Edit extends \Magento\Customer\Controller\Adminhtml\Group +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Customer\Controller\Adminhtml\Group implements HttpGetActionInterface { /** * Edit customer group action. Forward to new action. diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Group/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Group/Index.php index 1246b6a6d0b..6da79d2c40f 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Group/Index.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Group/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Customer\Controller\Adminhtml\Group; -class Index extends \Magento\Customer\Controller\Adminhtml\Group +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Customer\Controller\Adminhtml\Group implements HttpGetActionInterface { /** * Customer groups list. diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Group/NewAction.php b/app/code/Magento/Customer/Controller/Adminhtml/Group/NewAction.php index 2c230cd0b3f..a5c832bf0f1 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Group/NewAction.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Group/NewAction.php @@ -6,9 +6,10 @@ */ namespace Magento\Customer\Controller\Adminhtml\Group; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\Controller\RegistryConstants; -class NewAction extends \Magento\Customer\Controller\Adminhtml\Group +class NewAction extends \Magento\Customer\Controller\Adminhtml\Group implements HttpGetActionInterface { /** * Initialize current group and set it in the registry. diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php index 936d9cdbc17..7549315f9ff 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Group/Save.php @@ -6,11 +6,12 @@ */ namespace Magento\Customer\Controller\Adminhtml\Group; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\Data\GroupInterfaceFactory; use Magento\Customer\Api\Data\GroupInterface; use Magento\Customer\Api\GroupRepositoryInterface; -class Save extends \Magento\Customer\Controller\Adminhtml\Group +class Save extends \Magento\Customer\Controller\Adminhtml\Group implements HttpPostActionInterface { /** * @var \Magento\Framework\Reflection\DataObjectProcessor diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php index 7a981b82b7e..15da8b20adb 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php @@ -5,9 +5,10 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class Delete extends \Magento\Customer\Controller\Adminhtml\Index +class Delete extends \Magento\Customer\Controller\Adminhtml\Index implements HttpPostActionInterface { /** * Delete customer action diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Edit.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Edit.php index 90417c1ad54..25b4ddd4e17 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Edit.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Edit.php @@ -5,10 +5,11 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\Exception\NoSuchEntityException; -class Edit extends \Magento\Customer\Controller\Adminhtml\Index +class Edit extends \Magento\Customer\Controller\Adminhtml\Index implements HttpGetActionInterface { /** * Customer edit action diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Index.php index 861ac93b262..b1986c8b6f0 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Index.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Index.php @@ -5,7 +5,9 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; -class Index extends \Magento\Customer\Controller\Adminhtml\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Customer\Controller\Adminhtml\Index implements HttpGetActionInterface { /** * Customers list action diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php index 2d0ee3ae13d..3c3808d0a1e 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php @@ -6,16 +6,19 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Backend\App\Action; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\EmailNotificationInterface; -use Magento\Customer\Test\Block\Form\Login; use Magento\Customer\Ui\Component\Listing\AttributeRepository; -use Magento\Customer\Api\Data\CustomerInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Message\MessageInterface; /** + * Customer inline edit action + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class InlineEdit extends \Magento\Backend\App\Action +class InlineEdit extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -101,7 +104,11 @@ private function getEmailNotification() } /** + * Inline edit action execute + * * @return \Magento\Framework\Controller\Result\Json + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function execute() { @@ -139,7 +146,7 @@ public function execute() * Receive entity(customer|customer_address) data from request * * @param array $data - * @param null $isCustomerData + * @param mixed $isCustomerData * @return array */ protected function getData(array $data, $isCustomerData = null) @@ -249,7 +256,7 @@ protected function processAddressData(array $data) protected function getErrorMessages() { $messages = []; - foreach ($this->getMessageManager()->getMessages()->getItems() as $error) { + foreach ($this->getMessageManager()->getMessages()->getErrors() as $error) { $messages[] = $error->getText(); } return $messages; @@ -262,7 +269,7 @@ protected function getErrorMessages() */ protected function isErrorExists() { - return (bool)$this->getMessageManager()->getMessages(true)->getCount(); + return (bool)$this->getMessageManager()->getMessages(true)->getCountByType(MessageInterface::TYPE_ERROR); } /** diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php index 762b872b97b..a540ad9d7a7 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory; use Magento\Eav\Model\Entity\Collection\AbstractCollection; @@ -15,7 +16,7 @@ /** * Class MassAssignGroup */ -class MassAssignGroup extends AbstractMassAction +class MassAssignGroup extends AbstractMassAction implements HttpPostActionInterface { /** * @var CustomerRepositoryInterface diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php index 453585c881a..334018a881f 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory; use Magento\Eav\Model\Entity\Collection\AbstractCollection; @@ -15,7 +16,7 @@ /** * Class MassDelete */ -class MassDelete extends AbstractMassAction +class MassDelete extends AbstractMassAction implements HttpPostActionInterface { /** * @var CustomerRepositoryInterface diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/NewAction.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/NewAction.php index e6cf2aa234e..19a16f01acf 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/NewAction.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/NewAction.php @@ -5,7 +5,9 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; -class NewAction extends \Magento\Customer\Controller\Adminhtml\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Customer\Controller\Adminhtml\Index implements HttpGetActionInterface { /** * Create new customer action diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php index 37c8ed5a252..6ef6ddd0e73 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php @@ -5,10 +5,11 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\SecurityViolationException; -class ResetPassword extends \Magento\Customer\Controller\Adminhtml\Index +class ResetPassword extends \Magento\Customer\Controller\Adminhtml\Index implements HttpPostActionInterface { /** * Reset password handler diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 12732f81f78..45a7c0182d4 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Api\CustomerMetadataInterface; use Magento\Customer\Api\Data\CustomerInterface; @@ -16,7 +17,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Customer\Controller\Adminhtml\Index +class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpPostActionInterface { /** * @var EmailNotificationInterface diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php index 8e098a3b7ee..be09eb7daff 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php @@ -5,10 +5,13 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\Message\Error; +use Magento\Customer\Controller\Adminhtml\Index as CustomerAction; -class Validate extends \Magento\Customer\Controller\Adminhtml\Index +class Validate extends CustomerAction implements HttpPostActionInterface, HttpGetActionInterface { /** * Customer validation diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Online/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Online/Index.php index 3cf9a82b8b8..0262513d029 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Online/Index.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Online/Index.php @@ -6,10 +6,11 @@ */ namespace Magento\Customer\Controller\Adminhtml\Online; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class Index extends \Magento\Backend\App\Action +class Index extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Customer/Controller/Ajax/Login.php b/app/code/Magento/Customer/Controller/Ajax/Login.php index 73869bc3f29..7d1e86c9497 100644 --- a/app/code/Magento/Customer/Controller/Ajax/Login.php +++ b/app/code/Magento/Customer/Controller/Ajax/Login.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Ajax; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\AccountManagementInterface; use Magento\Framework\Exception\EmailNotConfirmedException; use Magento\Framework\Exception\InvalidEmailOrPasswordException; @@ -23,7 +24,7 @@ * @method \Magento\Framework\App\Response\Http getResponse() * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Login extends \Magento\Framework\App\Action\Action +class Login extends \Magento\Framework\App\Action\Action implements HttpPostActionInterface { /** * @var \Magento\Framework\Session\Generic diff --git a/app/code/Magento/Customer/Controller/Section/Load.php b/app/code/Magento/Customer/Controller/Section/Load.php index 7a2345c9175..e55b1d0df26 100644 --- a/app/code/Magento/Customer/Controller/Section/Load.php +++ b/app/code/Magento/Customer/Controller/Section/Load.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Section; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Customer\CustomerData\Section\Identifier; use Magento\Customer\CustomerData\SectionPoolInterface; use Magento\Framework\App\Action\Context; @@ -13,7 +14,7 @@ /** * Customer section controller */ -class Load extends \Magento\Framework\App\Action\Action +class Load extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { /** * @var JsonFactory diff --git a/app/code/Magento/Customer/Helper/Address.php b/app/code/Magento/Customer/Helper/Address.php index c74c62dc6d9..cfc76753fa1 100644 --- a/app/code/Magento/Customer/Helper/Address.php +++ b/app/code/Magento/Customer/Helper/Address.php @@ -127,6 +127,8 @@ public function getBookUrl() } /** + * Retrieve edit url. + * * @return void */ public function getEditUrl() @@ -134,6 +136,8 @@ public function getEditUrl() } /** + * Retrieve delete url. + * * @return void */ public function getDeleteUrl() @@ -141,6 +145,8 @@ public function getDeleteUrl() } /** + * Retrieve create url. + * * @return void */ public function getCreateUrl() @@ -148,6 +154,8 @@ public function getCreateUrl() } /** + * Retrieve block renderer. + * * @param string $renderer * @return \Magento\Framework\View\Element\BlockInterface */ @@ -204,6 +212,8 @@ public function getStreetLines($store = null) } /** + * Retrieve address format. + * * @param string $code * @return Format|string */ @@ -391,4 +401,23 @@ public function isAttributeVisible($code) } return false; } + + /** + * Retrieve attribute required + * + * @param string $code + * @return bool + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function isAttributeRequired($code) + { + $attributeMetadata = $this->_addressMetadataService->getAttributeMetadata($code); + + if ($attributeMetadata) { + return $attributeMetadata->isRequired(); + } + + return false; + } } diff --git a/app/code/Magento/Customer/Model/GroupManagement.php b/app/code/Magento/Customer/Model/GroupManagement.php index 47d7d7ad1ac..eb5d90f2fd7 100644 --- a/app/code/Magento/Customer/Model/GroupManagement.php +++ b/app/code/Magento/Customer/Model/GroupManagement.php @@ -8,16 +8,20 @@ namespace Magento\Customer\Model; use Magento\Customer\Api\Data\GroupInterface; +use Magento\Customer\Api\Data\GroupInterfaceFactory; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Data\Collection; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Api\GroupRepositoryInterface; -use Magento\Customer\Api\Data\GroupInterfaceFactory; -use Magento\Customer\Model\GroupFactory; /** + * The class contains methods for getting information about a customer group + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GroupManagement implements \Magento\Customer\Api\GroupManagementInterface @@ -65,6 +69,11 @@ class GroupManagement implements \Magento\Customer\Api\GroupManagementInterface */ protected $filterBuilder; + /** + * @var SortOrderBuilder + */ + private $sortOrderBuilder; + /** * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $scopeConfig @@ -73,6 +82,7 @@ class GroupManagement implements \Magento\Customer\Api\GroupManagementInterface * @param GroupInterfaceFactory $groupDataFactory * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param FilterBuilder $filterBuilder + * @param SortOrderBuilder $sortOrderBuilder */ public function __construct( StoreManagerInterface $storeManager, @@ -81,7 +91,8 @@ public function __construct( GroupRepositoryInterface $groupRepository, GroupInterfaceFactory $groupDataFactory, SearchCriteriaBuilder $searchCriteriaBuilder, - FilterBuilder $filterBuilder + FilterBuilder $filterBuilder, + SortOrderBuilder $sortOrderBuilder = null ) { $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; @@ -90,10 +101,12 @@ public function __construct( $this->groupDataFactory = $groupDataFactory; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->filterBuilder = $filterBuilder; + $this->sortOrderBuilder = $sortOrderBuilder ?: ObjectManager::getInstance() + ->get(SortOrderBuilder::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function isReadonly($groupId) { @@ -107,7 +120,7 @@ public function isReadonly($groupId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefaultGroup($storeId = null) { @@ -133,7 +146,7 @@ public function getDefaultGroup($storeId = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getNotLoggedInGroup() { @@ -141,7 +154,7 @@ public function getNotLoggedInGroup() } /** - * {@inheritdoc} + * @inheritdoc */ public function getLoggedInGroups() { @@ -155,15 +168,20 @@ public function getLoggedInGroups() ->setConditionType('neq') ->setValue(self::CUST_GROUP_ALL) ->create(); + $groupNameSortOrder = $this->sortOrderBuilder + ->setField('customer_group_code') + ->setDirection(Collection::SORT_ORDER_ASC) + ->create(); $searchCriteria = $this->searchCriteriaBuilder ->addFilters($notLoggedInFilter) ->addFilters($groupAll) + ->addSortOrder($groupNameSortOrder) ->create(); return $this->groupRepository->getList($searchCriteria)->getItems(); } /** - * {@inheritdoc} + * @inheritdoc */ public function getAllCustomersGroup() { diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml new file mode 100644 index 00000000000..b62a647ebdf --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteCustomerGroupActionGroup"> + <arguments> + <argument name="customerGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminCustomerGroupsIndexPage.url}}" stepKey="goToAdminCustomerGroupIndexPage"/> + <waitForPageLoad time="30" stepKey="waitForCustomerGroupIndexPageLoad"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomerGroupIndexPage"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="cleanFiltersIfTheySet"/> + <fillField userInput="{{customerGroupName}}" selector="{{AdminDataGridHeaderSection.filterFieldInput('customer_group_code')}}" stepKey="fillNameFieldOnFiltersSection"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCustomerGroupGridActionsSection.selectButton('customerGroupName')}}" stepKey="clickSelectButton"/> + <click selector="{{AdminCustomerGroupGridActionsSection.deleteAction('customerGroupName')}}" stepKey="clickOnDeleteItem"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDeleteCustomerGroup"/> + <seeElement selector="{{AdminMessagesSection.success}}" stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml new file mode 100644 index 00000000000..1681a8e850c --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGroupByNameActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFilterCustomerGroupByNameActionGroup"> + <arguments> + <argument name="customerGroupName" type="string"/> + </arguments> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomerGroupIndexPage"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="cleanFiltersIfTheySet"/> + <fillField userInput="{{customerGroupName}}" selector="{{AdminDataGridHeaderSection.filterFieldInput('customer_group_code')}}" stepKey="fillNameFieldOnFiltersSection"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index baf67728438..6f5ade53e67 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -89,4 +89,17 @@ <var key="id" entityKey="id" entityType="customer"/> <data key="firstname">Jane</data> </entity> + <entity name="Simple_US_CA_Customer" type="customer"> + <data key="group_id">1</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_CA</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml index 523a463e5de..b1da921f7ca 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml @@ -15,7 +15,7 @@ <entity name="RegionTX" type="region"> <data key="region">Texas</data> <data key="region_code">TX</data> - <data key="region_id">1</data> + <data key="region_id">57</data> </entity> <entity name="RegionCA" type="region"> <data key="region">California</data> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerGroupsIndexPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerGroupsIndexPage.xml new file mode 100644 index 00000000000..5981fb7c907 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerGroupsIndexPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminCustomerGroupsIndexPage" url="/customer/group/" area="admin" module="Magento_Customer"> + <section name="AdminCustomerGroupGridActionsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml index c4f03659c12..eaca1c820e4 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerDashboardPage" url="/customer/account/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerDashboardAccountInformationSection" /> + <section name="StorefrontCustomerSidebarSection"/> </page> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupGridActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupGridActionsSection.xml new file mode 100644 index 00000000000..391292ca7fa --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupGridActionsSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerGroupGridActionsSection"> + <element name="selectButton" type="button" selector="//div[text()='{{groupName}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//button[text()='Select']" timeout="30" parameterized="true"/> + <element name="deleteAction" type="button" selector="//div[text()='{{groupName}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='Delete']" timeout="30" parameterized="true"/> + <element name="actionsMenuButton" type="text" selector="//div[text()='{{groupName}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='{{selectItem}}']" timeout="30" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml index 253a6a83c94..e8b11b27ddc 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml @@ -9,6 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerOrderSection"> + <element name="isMyOrdersSection" type="text" selector="//*[@class='page-title']//*[contains(text(), 'My Orders')]"/> <element name="productCustomOptions" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[normalize-space(.)='{{var3}}']" parameterized="true"/> <element name="productCustomOptionsFile" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[contains(.,'{{var3}}')]" parameterized="true"/> <element name="productCustomOptionsLink" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd//a[text() = '{{var3}}']" parameterized="true"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml index 53b07b2b6a5..81612d1c643 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml @@ -13,5 +13,6 @@ <element name="myOrdersTable" type="text" selector="#my-orders-table"/> <element name="subtotal" type="text" selector=".subtotal .amount"/> <element name="paymentMethod" type="text" selector=".payment-method dt"/> + <element name="printOrderLink" type="text" selector="a.action.print" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml new file mode 100644 index 00000000000..74821930310 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSidebarSection"> + <element name="sidebarTab" type="text" selector="//div[@id='block-collapsible-nav']//a[text()='{{var1}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php index 913c4107085..78d9dd70035 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; use Magento\Customer\Model\EmailNotificationInterface; +use Magento\Framework\Message\MessageInterface; /** * @SuppressWarnings(PHPMD.TooManyFields) @@ -242,10 +243,11 @@ protected function prepareMocksForErrorMessagesProcessing() ->method('getMessages') ->willReturn($this->messageCollection); $this->messageCollection->expects($this->once()) - ->method('getItems') + ->method('getErrors') ->willReturn([$this->message]); $this->messageCollection->expects($this->once()) - ->method('getCount') + ->method('getCountByType') + ->with(MessageInterface::TYPE_ERROR) ->willReturn(1); $this->message->expects($this->once()) ->method('getText') diff --git a/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php b/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php index 74af4ec57c7..fd06f778efe 100644 --- a/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php +++ b/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php @@ -414,4 +414,43 @@ public function isAttributeVisibleDataProvider() ['invalid_code', false] ]; } + + /** + * Test is required filed by attribute code + * + * @param string $attributeCode + * @param bool $isMetadataExists + * @dataProvider isAttributeRequiredDataProvider + * @covers \Magento\Customer\Helper\Address::isAttributeRequired() + * @return void + */ + public function testIsAttributeRequired($attributeCode, $isMetadataExists) + { + $attributeMetadata = null; + if ($isMetadataExists) { + $attributeMetadata = $this->getMockBuilder(\Magento\Customer\Api\Data\AttributeMetadataInterface::class) + ->getMockForAbstractClass(); + $attributeMetadata->expects($this->once()) + ->method('isRequired') + ->willReturn(true); + } + $this->addressMetadataService->expects($this->once()) + ->method('getAttributeMetadata') + ->with($attributeCode) + ->willReturn($attributeMetadata); + $this->assertEquals($isMetadataExists, $this->helper->isAttributeRequired($attributeCode)); + } + + /** + * Data provider for test testIsAttributeRequire + * + * @return array + */ + public function isAttributeRequiredDataProvider() + { + return [ + ['fax', true], + ['invalid_code', false] + ]; + } } diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php deleted file mode 100644 index fdd841ea88c..00000000000 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/GroupActionsTest.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Customer\Test\Unit\Ui\Component\Listing\Column; - -use Magento\Customer\Ui\Component\Listing\Column\GroupActions; -use Magento\Framework\Escaper; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Framework\UrlInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponent\Processor; -use PHPUnit_Framework_MockObject_MockObject as MockObject; - -/** - * GroupActionsTest contains unit tests for \Magento\Customer\Ui\Component\Listing\Column\GroupActions class - */ -class GroupActionsTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var GroupActions - */ - protected $groupActions; - - /** - * @var Escaper|MockObject - */ - protected $escaper; - - /** - * @var UrlInterface|MockObject - */ - protected $urlBuilder; - - /** - * SetUp method - * - * @return void - */ - protected function setUp() - { - $objectManager = new ObjectManager($this); - - $context = $this->createMock(ContextInterface::class); - - $processor = $this->getMockBuilder(Processor::class) - ->disableOriginalConstructor() - ->getMock(); - $context->expects(static::never()) - ->method('getProcessor') - ->willReturn($processor); - - $this->urlBuilder = $this->createMock(UrlInterface::class); - - $this->escaper = $this->getMockBuilder(Escaper::class) - ->disableOriginalConstructor() - ->setMethods(['escapeHtml']) - ->getMock(); - - $this->groupActions = $objectManager->getObject(GroupActions::class, [ - 'context' => $context, - 'urlBuilder' => $this->urlBuilder, - 'escaper' => $this->escaper, - ]); - } - - /** - * @covers \Magento\Customer\Ui\Component\Listing\Column\GroupActions::prepareDataSource - */ - public function testPrepareDataSource() - { - $groupId = 1; - $groupCode = 'group code'; - $items = [ - 'data' => [ - 'items' => [ - [ - 'customer_group_id' => $groupId, - 'customer_group_code' => $groupCode - ] - ] - ] - ]; - $name = 'item_name'; - $expectedItems = [ - [ - 'customer_group_id' => $groupId, - 'customer_group_code' => $groupCode, - $name => [ - 'edit' => [ - 'href' => 'test/url/edit', - 'label' => __('Edit'), - ], - 'delete' => [ - 'href' => 'test/url/delete', - 'label' => __('Delete'), - 'confirm' => [ - 'title' => __('Delete %1', $groupCode), - 'message' => __('Are you sure you want to delete a %1 record?', $groupCode) - ], - ] - ], - ] - ]; - - $this->escaper->expects(static::once()) - ->method('escapeHtml') - ->with($groupCode) - ->willReturn($groupCode); - - $this->urlBuilder->expects(static::exactly(2)) - ->method('getUrl') - ->willReturnMap( - [ - [ - GroupActions::URL_PATH_EDIT, - [ - 'id' => $groupId - ], - 'test/url/edit', - ], - [ - GroupActions::URL_PATH_DELETE, - [ - 'id' => $groupId - ], - 'test/url/delete', - ], - ] - ); - - $this->groupActions->setData('name', $name); - - $actual = $this->groupActions->prepareDataSource($items); - static::assertEquals($expectedItems, $actual['data']['items']); - } -} diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php index a8a3429ebad..00c5f99fab4 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/GroupActions.php @@ -98,7 +98,8 @@ public function prepareDataSource(array $dataSource) 'confirm' => [ 'title' => __('Delete %1', $title), 'message' => __('Are you sure you want to delete a %1 record?', $title) - ] + ], + 'post' => true ]; } } diff --git a/app/code/Magento/Customer/etc/db_schema.xml b/app/code/Magento/Customer/etc/db_schema.xml index 368ca417432..657083b58e1 100644 --- a/app/code/Magento/Customer/etc/db_schema.xml +++ b/app/code/Magento/Customer/etc/db_schema.xml @@ -9,15 +9,15 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="customer_entity" resource="default" engine="innodb" comment="Customer Entity"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Website Id"/> + comment="Website ID"/> <column xsi:type="varchar" name="email" nullable="true" length="255" comment="Email"/> <column xsi:type="smallint" name="group_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Group Id"/> + default="0" comment="Group ID"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - default="0" comment="Store Id"/> + default="0" comment="Store ID"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" @@ -77,10 +77,10 @@ </table> <table name="customer_address_entity" resource="default" engine="innodb" comment="Customer Address Entity"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="true" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" @@ -128,7 +128,7 @@ <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Attribute Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="datetime" name="value" on_update="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -159,7 +159,7 @@ <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Attribute Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -186,11 +186,11 @@ </table> <table name="customer_address_entity_int" resource="default" engine="innodb" comment="Customer Address Entity Int"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="int" name="value" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -218,11 +218,11 @@ <table name="customer_address_entity_text" resource="default" engine="innodb" comment="Customer Address Entity Text"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="text" name="value" nullable="false" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -244,11 +244,11 @@ <table name="customer_address_entity_varchar" resource="default" engine="innodb" comment="Customer Address Entity Varchar"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="value" nullable="true" length="255" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -274,11 +274,11 @@ </table> <table name="customer_entity_datetime" resource="default" engine="innodb" comment="Customer Entity Datetime"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="datetime" name="value" on_update="false" nullable="true" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -304,11 +304,11 @@ </table> <table name="customer_entity_decimal" resource="default" engine="innodb" comment="Customer Entity Decimal"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -335,11 +335,11 @@ </table> <table name="customer_entity_int" resource="default" engine="innodb" comment="Customer Entity Int"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="int" name="value" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -366,11 +366,11 @@ </table> <table name="customer_entity_text" resource="default" engine="innodb" comment="Customer Entity Text"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="text" name="value" nullable="false" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -391,11 +391,11 @@ </table> <table name="customer_entity_varchar" resource="default" engine="innodb" comment="Customer Entity Varchar"> <column xsi:type="int" name="value_id" padding="11" unsigned="false" nullable="false" identity="true" - comment="Value Id"/> + comment="Value ID"/> <column xsi:type="smallint" name="attribute_id" padding="5" unsigned="true" nullable="false" identity="false" - default="0" comment="Attribute Id"/> + default="0" comment="Attribute ID"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="value" nullable="true" length="255" comment="Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index 4de6644b948..d2a9b3f4462 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -486,9 +486,6 @@ </item> </argument> <settings> - <validation> - <rule name="required-entry" xsi:type="boolean">true</rule> - </validation> <dataType>text</dataType> </settings> </field> diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/ChangeSubscriptionStatus.php b/app/code/Magento/CustomerGraphQl/Model/Customer/ChangeSubscriptionStatus.php new file mode 100644 index 00000000000..1acb418e7bb --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/ChangeSubscriptionStatus.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Customer; + +use Magento\Newsletter\Model\SubscriberFactory; + +/** + * Change subscription status. Subscribe OR unsubscribe if required + */ +class ChangeSubscriptionStatus +{ + /** + * @var SubscriberFactory + */ + private $subscriberFactory; + + /** + * @param SubscriberFactory $subscriberFactory + */ + public function __construct( + SubscriberFactory $subscriberFactory + ) { + $this->subscriberFactory = $subscriberFactory; + } + + /** + * Change subscription status. Subscribe OR unsubscribe if required + * + * @param int $customerId + * @param bool $subscriptionStatus + * @return void + */ + public function execute(int $customerId, bool $subscriptionStatus): void + { + $subscriber = $this->subscriberFactory->create()->loadByCustomerId($customerId); + + if ($subscriptionStatus === true && !$subscriber->isSubscribed()) { + $this->subscriberFactory->create()->subscribeCustomerById($customerId); + } elseif ($subscriptionStatus === false && $subscriber->isSubscribed()) { + $this->subscriberFactory->create()->unsubscribeCustomerById($customerId); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php new file mode 100644 index 00000000000..030fc47d19e --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Customer; + +use Magento\Authorization\Model\UserContextInterface; +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\AuthenticationInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; + +/** + * Check customer account + */ +class CheckCustomerAccount +{ + /** + * @var AuthenticationInterface + */ + private $authentication; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var AccountManagementInterface + */ + private $accountManagement; + + /** + * @param AuthenticationInterface $authentication + * @param CustomerRepositoryInterface $customerRepository + * @param AccountManagementInterface $accountManagement + */ + public function __construct( + AuthenticationInterface $authentication, + CustomerRepositoryInterface $customerRepository, + AccountManagementInterface $accountManagement + ) { + $this->authentication = $authentication; + $this->customerRepository = $customerRepository; + $this->accountManagement = $accountManagement; + } + + /** + * Check customer account + * + * @param int|null $customerId + * @param int|null $customerType + * @return void + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException + * @throws GraphQlAuthenticationException + */ + public function execute(?int $customerId, ?int $customerType): void + { + if (true === $this->isCustomerGuest($customerId, $customerType)) { + throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); + } + + try { + $this->customerRepository->getById($customerId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException( + __('Customer with id "%customer_id" does not exist.', ['customer_id' => $customerId]), + $e + ); + } + + if (true === $this->authentication->isLocked($customerId)) { + throw new GraphQlAuthenticationException(__('The account is locked.')); + } + + $confirmationStatus = $this->accountManagement->getConfirmationStatus($customerId); + if ($confirmationStatus === AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED) { + throw new GraphQlAuthenticationException(__("This account isn't confirmed. Verify and try again.")); + } + } + + /** + * Checking if current customer is guest + * + * @param int|null $customerId + * @param int|null $customerType + * @return bool + */ + private function isCustomerGuest(?int $customerId, ?int $customerType): bool + { + if (null === $customerId || null === $customerType) { + return true; + } + return 0 === (int)$customerId || (int)$customerType === UserContextInterface::USER_TYPE_GUEST; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php new file mode 100644 index 00000000000..f3c03e5fc18 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Customer; + +use Magento\Customer\Model\AuthenticationInterface; +use Magento\Framework\Exception\InvalidEmailOrPasswordException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; + +/** + * Check customer password + */ +class CheckCustomerPassword +{ + /** + * @var AuthenticationInterface + */ + private $authentication; + + /** + * @param AuthenticationInterface $authentication + */ + public function __construct( + AuthenticationInterface $authentication + ) { + $this->authentication = $authentication; + } + + /** + * Check customer password + * + * @param string $password + * @param int $customerId + * @throws GraphQlAuthenticationException + */ + public function execute(string $password, int $customerId) + { + try { + $this->authentication->authenticate($customerId, $password); + } catch (InvalidEmailOrPasswordException $e) { + throw new GraphQlAuthenticationException( + __('The password doesn\'t match this account. Verify the password and try again.') + ); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php similarity index 68% rename from app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php rename to app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php index 1a942a2ab14..646d426050b 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php @@ -5,11 +5,12 @@ */ declare(strict_types=1); -namespace Magento\CustomerGraphQl\Model\Resolver\Customer; +namespace Magento\CustomerGraphQl\Model\Customer; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Webapi\ServiceOutputProcessor; use Magento\Customer\Api\Data\CustomerInterface; @@ -32,21 +33,21 @@ class CustomerDataProvider /** * @var SerializerInterface */ - private $jsonSerializer; + private $serializer; /** * @param CustomerRepositoryInterface $customerRepository * @param ServiceOutputProcessor $serviceOutputProcessor - * @param SerializerInterface $jsonSerializer + * @param SerializerInterface $serializer */ public function __construct( CustomerRepositoryInterface $customerRepository, ServiceOutputProcessor $serviceOutputProcessor, - SerializerInterface $jsonSerializer + SerializerInterface $serializer ) { $this->customerRepository = $customerRepository; $this->serviceOutputProcessor = $serviceOutputProcessor; - $this->jsonSerializer = $jsonSerializer; + $this->serializer = $serializer; } /** @@ -56,42 +57,44 @@ public function __construct( * @return array * @throws NoSuchEntityException|LocalizedException */ - public function getCustomerById(int $customerId) : array + public function getCustomerById(int $customerId): array { try { - $customerObject = $this->customerRepository->getById($customerId); + $customer = $this->customerRepository->getById($customerId); } catch (NoSuchEntityException $e) { - // No error should be thrown, null result should be returned - return []; + throw new GraphQlNoSuchEntityException( + __('Customer id "%customer_id" does not exist.', ['customer_id' => $customerId]), + $e + ); } - return $this->processCustomer($customerObject); + return $this->processCustomer($customer); } /** * Transform single customer data from object to in array format * - * @param CustomerInterface $customerObject + * @param CustomerInterface $customer * @return array */ - private function processCustomer(CustomerInterface $customerObject) : array + private function processCustomer(CustomerInterface $customer): array { - $customer = $this->serviceOutputProcessor->process( - $customerObject, + $customerData = $this->serviceOutputProcessor->process( + $customer, CustomerRepositoryInterface::class, 'get' ); - if (isset($customer['extension_attributes'])) { - $customer = array_merge($customer, $customer['extension_attributes']); + if (isset($customerData['extension_attributes'])) { + $customerData = array_merge($customerData, $customerData['extension_attributes']); } $customAttributes = []; - if (isset($customer['custom_attributes'])) { - foreach ($customer['custom_attributes'] as $attribute) { + if (isset($customerData['custom_attributes'])) { + foreach ($customerData['custom_attributes'] as $attribute) { $isArray = false; if (is_array($attribute['value'])) { $isArray = true; foreach ($attribute['value'] as $attributeValue) { if (is_array($attributeValue)) { - $customAttributes[$attribute['attribute_code']] = $this->jsonSerializer->serialize( + $customAttributes[$attribute['attribute_code']] = $this->serializer->serialize( $attribute['value'] ); continue; @@ -106,8 +109,8 @@ private function processCustomer(CustomerInterface $customerObject) : array $customAttributes[$attribute['attribute_code']] = $attribute['value']; } } - $customer = array_merge($customer, $customAttributes); + $customerData = array_merge($customerData, $customAttributes); - return $customer; + return $customerData; } } diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php new file mode 100644 index 00000000000..d235d51b5e5 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Customer; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Framework\GraphQl\Exception\GraphQlAlreadyExistsException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Update account information + */ +class UpdateAccountInformation +{ + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CheckCustomerPassword + */ + private $checkCustomerPassword; + + /** + * @param CustomerRepositoryInterface $customerRepository + * @param StoreManagerInterface $storeManager + * @param CheckCustomerPassword $checkCustomerPassword + */ + public function __construct( + CustomerRepositoryInterface $customerRepository, + StoreManagerInterface $storeManager, + CheckCustomerPassword $checkCustomerPassword + ) { + $this->customerRepository = $customerRepository; + $this->storeManager = $storeManager; + $this->checkCustomerPassword = $checkCustomerPassword; + } + + /** + * Update account information + * + * @param int $customerId + * @param array $data + * @return void + * @throws GraphQlNoSuchEntityException + * @throws GraphQlInputException + * @throws GraphQlAlreadyExistsException + */ + public function execute(int $customerId, array $data): void + { + $customer = $this->customerRepository->getById($customerId); + + if (isset($data['firstname'])) { + $customer->setFirstname($data['firstname']); + } + + if (isset($data['lastname'])) { + $customer->setLastname($data['lastname']); + } + + if (isset($data['email']) && $customer->getEmail() !== $data['email']) { + if (!isset($data['password']) || empty($data['password'])) { + throw new GraphQlInputException(__('For changing "email" you should provide current "password".')); + } + + $this->checkCustomerPassword->execute($data['password'], $customerId); + $customer->setEmail($data['email']); + } + + $customer->setStoreId($this->storeManager->getStore()->getId()); + + try { + $this->customerRepository->save($customer); + } catch (AlreadyExistsException $e) { + throw new GraphQlAlreadyExistsException( + __('A customer with the same email address already exists in an associated website.'), + $e + ); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php new file mode 100644 index 00000000000..98b0975b371 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerPassword; +use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider; +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * @inheritdoc + */ +class ChangePassword implements ResolverInterface +{ + /** + * @var CheckCustomerAccount + */ + private $checkCustomerAccount; + + /** + * @var CheckCustomerPassword + */ + private $checkCustomerPassword; + + /** + * @var AccountManagementInterface + */ + private $accountManagement; + + /** + * @var CustomerDataProvider + */ + private $customerDataProvider; + + /** + * @param CheckCustomerAccount $checkCustomerAccount + * @param CheckCustomerPassword $checkCustomerPassword + * @param AccountManagementInterface $accountManagement + * @param CustomerDataProvider $customerDataProvider + */ + public function __construct( + CheckCustomerAccount $checkCustomerAccount, + CheckCustomerPassword $checkCustomerPassword, + AccountManagementInterface $accountManagement, + CustomerDataProvider $customerDataProvider + ) { + $this->checkCustomerAccount = $checkCustomerAccount; + $this->checkCustomerPassword = $checkCustomerPassword; + $this->accountManagement = $accountManagement; + $this->customerDataProvider = $customerDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['currentPassword'])) { + throw new GraphQlInputException(__('"currentPassword" value should be specified')); + } + + if (!isset($args['newPassword'])) { + throw new GraphQlInputException(__('"newPassword" value should be specified')); + } + + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + $currentUserId = (int)$currentUserId; + $this->checkCustomerPassword->execute($args['currentPassword'], $currentUserId); + + $this->accountManagement->changePasswordById($currentUserId, $args['currentPassword'], $args['newPassword']); + + $data = $this->customerDataProvider->getCustomerById($currentUserId); + return $data; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php index 98941af3b73..c3c78a1004d 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php @@ -7,14 +7,10 @@ namespace Magento\CustomerGraphQl\Model\Resolver; -use Magento\Authorization\Model\UserContextInterface; +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\CustomerGraphQl\Model\Resolver\Customer\CustomerDataProvider; -use Magento\Framework\Exception\NoSuchEntityException; +use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; use Magento\Framework\GraphQl\Query\ResolverInterface; /** @@ -22,18 +18,26 @@ */ class Customer implements ResolverInterface { + /** + * @var CheckCustomerAccount + */ + private $checkCustomerAccount; + /** * @var CustomerDataProvider */ - private $customerResolver; + private $customerDataProvider; /** - * @param CustomerDataProvider $customerResolver + * @param CheckCustomerAccount $checkCustomerAccount + * @param CustomerDataProvider $customerDataProvider */ public function __construct( - CustomerDataProvider $customerResolver + CheckCustomerAccount $checkCustomerAccount, + CustomerDataProvider $customerDataProvider ) { - $this->customerResolver = $customerResolver; + $this->checkCustomerAccount = $checkCustomerAccount; + $this->customerDataProvider = $customerDataProvider; } /** @@ -46,21 +50,13 @@ public function resolve( array $value = null, array $args = null ) { - /** @var ContextInterface $context */ - if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) { - throw new GraphQlAuthorizationException( - __( - 'Current customer does not have access to the resource "%1"', - [\Magento\Customer\Model\Customer::ENTITY] - ) - ); - } + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); - try { - $data = $this->customerResolver->getCustomerById($context->getUserId()); - return !empty($data) ? $data : []; - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException(__('Customer id %1 does not exist.', [$context->getUserId()])); - } + $currentUserId = (int)$currentUserId; + $data = $this->customerDataProvider->getCustomerById($currentUserId); + return $data; } } diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php new file mode 100644 index 00000000000..9719d048f60 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Integration\Api\CustomerTokenServiceInterface; + +/** + * Customers Token resolver, used for GraphQL request processing. + */ +class GenerateCustomerToken implements ResolverInterface +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @param CustomerTokenServiceInterface $customerTokenService + */ + public function __construct( + CustomerTokenServiceInterface $customerTokenService + ) { + $this->customerTokenService = $customerTokenService; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['email'])) { + throw new GraphQlInputException(__('"email" value should be specified')); + } + + if (!isset($args['password'])) { + throw new GraphQlInputException(__('"password" value should be specified')); + } + + try { + $token = $this->customerTokenService->createCustomerAccessToken($args['email'], $args['password']); + return ['token' => $token]; + } catch (AuthenticationException $e) { + throw new GraphQlAuthenticationException(__($e->getMessage()), $e); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php new file mode 100644 index 00000000000..c0bd864b3ee --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Newsletter\Model\SubscriberFactory; + +/** + * Customer is_subscribed field resolver + */ +class IsSubscribed implements ResolverInterface +{ + /** + * @var CheckCustomerAccount + */ + private $checkCustomerAccount; + + /** + * @var SubscriberFactory + */ + private $subscriberFactory; + + /** + * @param CheckCustomerAccount $checkCustomerAccount + * @param SubscriberFactory $subscriberFactory + */ + public function __construct( + CheckCustomerAccount $checkCustomerAccount, + SubscriberFactory $subscriberFactory + ) { + $this->checkCustomerAccount = $checkCustomerAccount; + $this->subscriberFactory = $subscriberFactory; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + $status = $this->subscriberFactory->create()->loadByCustomerId((int)$currentUserId)->isSubscribed(); + return (bool)$status; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php new file mode 100644 index 00000000000..d3b16c05a64 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Integration\Api\CustomerTokenServiceInterface; + +/** + * Customers Revoke Token resolver, used for GraphQL request processing. + */ +class RevokeCustomerToken implements ResolverInterface +{ + /** + * @var CheckCustomerAccount + */ + private $checkCustomerAccount; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @param CheckCustomerAccount $checkCustomerAccount + * @param CustomerTokenServiceInterface $customerTokenService + */ + public function __construct( + CheckCustomerAccount $checkCustomerAccount, + CustomerTokenServiceInterface $customerTokenService + ) { + $this->checkCustomerAccount = $checkCustomerAccount; + $this->customerTokenService = $customerTokenService; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + return $this->customerTokenService->revokeCustomerAccessToken((int)$currentUserId); + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php new file mode 100644 index 00000000000..5dc857f3f17 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\CustomerGraphQl\Model\Customer\ChangeSubscriptionStatus; +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; +use Magento\CustomerGraphQl\Model\Customer\UpdateAccountInformation; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; + +/** + * Update customer data resolver + */ +class UpdateCustomer implements ResolverInterface +{ + /** + * @var CheckCustomerAccount + */ + private $checkCustomerAccount; + + /** + * @var UpdateAccountInformation + */ + private $updateAccountInformation; + + /** + * @var ChangeSubscriptionStatus + */ + private $changeSubscriptionStatus; + + /** + * @var CustomerDataProvider + */ + private $customerDataProvider; + + /** + * @param CheckCustomerAccount $checkCustomerAccount + * @param UpdateAccountInformation $updateAccountInformation + * @param ChangeSubscriptionStatus $changeSubscriptionStatus + * @param CustomerDataProvider $customerDataProvider + */ + public function __construct( + CheckCustomerAccount $checkCustomerAccount, + UpdateAccountInformation $updateAccountInformation, + ChangeSubscriptionStatus $changeSubscriptionStatus, + CustomerDataProvider $customerDataProvider + ) { + $this->checkCustomerAccount = $checkCustomerAccount; + $this->updateAccountInformation = $updateAccountInformation; + $this->changeSubscriptionStatus = $changeSubscriptionStatus; + $this->customerDataProvider = $customerDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['input']) || !is_array($args['input']) || empty($args['input'])) { + throw new GraphQlInputException(__('"input" value should be specified')); + } + + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + $currentUserId = (int)$currentUserId; + $this->updateAccountInformation->execute($currentUserId, $args['input']); + + if (isset($args['input']['is_subscribed'])) { + $this->changeSubscriptionStatus->execute($currentUserId, (bool)$args['input']['is_subscribed']); + } + + $data = $this->customerDataProvider->getCustomerById($currentUserId); + return ['customer' => $data]; + } +} diff --git a/app/code/Magento/CustomerGraphQl/composer.json b/app/code/Magento/CustomerGraphQl/composer.json index 290d925215e..488013c1ee5 100644 --- a/app/code/Magento/CustomerGraphQl/composer.json +++ b/app/code/Magento/CustomerGraphQl/composer.json @@ -6,6 +6,9 @@ "php": "~7.1.3||~7.2.0", "magento/module-customer": "*", "magento/module-authorization": "*", + "magento/module-newsletter": "*", + "magento/module-integration": "*", + "magento/module-store": "*", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index 91b7ef1f9be..b8411f00c5c 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -5,6 +5,29 @@ type Query { customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account") } +type Mutation { + generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve Customer token") + changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes password for logged in customer") + updateCustomer (input: UpdateCustomerInput): UpdateCustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update customer personal information") + revokeCustomerToken: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke Customer token") +} + +type CustomerToken { + token: String @doc(description: "The customer token") +} + +input UpdateCustomerInput { + firstname: String + lastname: String + email: String + password: String + is_subscribed: Boolean +} + +type UpdateCustomerOutput { + customer: Customer! +} + type Customer @doc(description: "Customer defines the customer name and address and other details") { created_at: String @doc(description: "Timestamp indicating when the account was created") group_id: Int @doc(description: "The group assigned to the user. Default values are 0 (Not logged in), 1 (General), 2 (Wholesale), and 3 (Retailer)") @@ -19,9 +42,9 @@ type Customer @doc(description: "Customer defines the customer name and address dob: String @doc(description: "The customer's date of birth") taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)") id: Int @doc(description: "The ID assigned to the customer") - is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") + is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\IsSubscribed") addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses") -} +} type CustomerAddress @doc(description: "CustomerAddress contains detailed information about a customer's billing and shipping addresses"){ id: Int @doc(description: "The ID assigned to the address object") @@ -50,4 +73,3 @@ type CustomerAddressRegion @doc(description: "CustomerAddressRegion defines the region: String @doc(description: "The state or province name") region_id: Int @doc(description: "Uniquely identifies the region") } - diff --git a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php index a4a9ef00c4a..efcc12e0894 100644 --- a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php +++ b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php @@ -110,6 +110,8 @@ public function __construct( } /** + * Retrieves sections names + * * Retrieves names of sections for configuration files whose data is read from these files for import * to appropriate application sources. * @@ -187,14 +189,7 @@ public function getValidator($section) private function sort(array $data) { uasort($data, function (array $a, array $b) { - $a['sort_order'] = $this->getSortOrder($a); - $b['sort_order'] = $this->getSortOrder($b); - - if ($a['sort_order'] == $b['sort_order']) { - return 0; - } - - return ($a['sort_order'] < $b['sort_order']) ? -1 : 1; + return $this->getSortOrder($a) <=> $this->getSortOrder($b); }); return $data; diff --git a/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php b/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php index cc3760b6846..d224c13b5a6 100644 --- a/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php +++ b/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Developer\Console\Command; @@ -25,7 +26,7 @@ class GeneratePatchCommand extends Command /** * Command arguments and options */ - const COMMAND_NAME = 'dev:generate:patch'; + const COMMAND_NAME = 'setup:db-declaration:generate-patch'; const MODULE_NAME = 'module'; const INPUT_KEY_IS_REVERTABLE = 'revertable'; const INPUT_KEY_PATCH_TYPE = 'type'; @@ -47,7 +48,9 @@ public function __construct(ComponentRegistrar $componentRegistrar) } /** - * {@inheritdoc} + * Configure command + * + * @inheritdoc * @throws InvalidArgumentException */ protected function configure() @@ -89,16 +92,16 @@ protected function configure() * * @return string */ - private function getPatchTemplate() + private function getPatchTemplate() : string { return file_get_contents(__DIR__ . '/patch_template.php.dist'); } /** - * {@inheritdoc} + * @inheritdoc * @throws \InvalidArgumentException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output) : int { $moduleName = $input->getArgument(self::MODULE_NAME); $patchName = $input->getArgument(self::INPUT_KEY_PATCH_NAME); diff --git a/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php b/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php index d283e693b8d..b30edc0c797 100644 --- a/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php +++ b/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Developer\Console\Command; use Magento\Framework\Component\ComponentRegistrar; @@ -18,6 +20,7 @@ /** * Command that allows to generate whitelist, that will be used, when declaration data is installed. + * * If whitelist already exists, new values will be added to existing whitelist. */ class TablesWhitelistGenerateCommand extends Command @@ -72,7 +75,7 @@ public function __construct( */ protected function configure() { - $this->setName('declaration:generate:whitelist') + $this->setName('setup:db-declaration:generate-whitelist') ->setDescription( 'Generate whitelist of tables and columns that are allowed to be edited by declaration installer' ) @@ -125,7 +128,7 @@ private function persistModule($moduleName) /** * @inheritdoc */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output) : int { $moduleName = $input->getOption(self::MODULE_NAME_KEY); @@ -146,13 +149,14 @@ protected function execute(InputInterface $input, OutputInterface $output) } /** - * As for whitelist we do not need any specific attributes like nullable or indexType, - * we need to choose only names. + * Filter attribute names + * + * As for whitelist we do not need any specific attributes like nullable or indexType, we need to choose only names. * * @param array $content * @return array */ - private function filterAttributeNames(array $content) + private function filterAttributeNames(array $content) : array { $names = []; $types = ['column', 'index', 'constraint']; diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php index 38444de78ab..83b2797050d 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php @@ -1,14 +1,19 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\File; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class Upload extends \Magento\Downloadable\Controller\Adminhtml\Downloadable\File +/** + * Class Upload + * + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\File + */ +class Upload extends \Magento\Downloadable\Controller\Adminhtml\Downloadable\File implements HttpPostActionInterface { /** * @var \Magento\Downloadable\Model\Link @@ -96,17 +101,10 @@ public function execute() $relativePath = rtrim($tmpPath, '/') . '/' . ltrim($result['file'], '/'); $this->storageDatabase->saveFile($relativePath); } - - $result['cookie'] = [ - 'name' => $this->_getSession()->getName(), - 'value' => $this->_getSession()->getSessionId(), - 'lifetime' => $this->_getSession()->getCookieLifetime(), - 'path' => $this->_getSession()->getCookiePath(), - 'domain' => $this->_getSession()->getCookieDomain(), - ]; } catch (\Exception $e) { $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } + return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result); } } diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml index 6e3e7f7d171..3ee6cef4773 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml @@ -17,9 +17,6 @@ <severity value="MAJOR"/> <testCaseId value="MC-201"/> <group value="Downloadable"/> - <skip> - <issueId value="MC-4063"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php b/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php index df1f127fae3..281fb571ff5 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php @@ -59,11 +59,6 @@ class UploadTest extends \PHPUnit\Framework\TestCase */ protected $fileHelper; - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Session - */ - protected $session; - /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Controller\ResultFactory */ @@ -81,9 +76,6 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->session = $this->getMockBuilder(\Magento\Backend\Model\Session::class) - ->disableOriginalConstructor() - ->getMock(); $this->resultFactory = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) @@ -108,9 +100,6 @@ protected function setUp() $this->context->expects($this->any()) ->method('getRequest') ->will($this->returnValue($this->request)); - $this->context->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($this->session)); $this->context->expects($this->any()) ->method('getResultFactory') ->will($this->returnValue($this->resultFactory)); @@ -154,11 +143,6 @@ public function testExecute() $this->uploaderFactory->expects($this->once())->method('create')->willReturn($uploader); $this->fileHelper->expects($this->once())->method('uploadFromTmp')->willReturn($data); $this->storageDatabase->expects($this->once())->method('saveFile'); - $this->session->expects($this->once())->method('getName')->willReturn('Name'); - $this->session->expects($this->once())->method('getSessionId')->willReturn('SessionId'); - $this->session->expects($this->once())->method('getCookieLifetime')->willReturn('CookieLifetime'); - $this->session->expects($this->once())->method('getCookiePath')->willReturn('CookiePath'); - $this->session->expects($this->once())->method('getCookieDomain')->willReturn('CookieDomain'); $this->resultFactory->expects($this->once())->method('create')->willReturn($resultJson); $resultJson->expects($this->once())->method('setData')->willReturnSelf(); diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index 6601c050513..29ead9ff810 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -216,6 +216,7 @@ public function __construct( /** * Get Serializer instance. + * * @deprecated 100.2.0 * * @return Json @@ -244,8 +245,8 @@ protected function _construct() /** * Load attribute data by code * - * @param string|int|\Magento\Eav\Model\Entity\Type $entityType - * @param string $code + * @param string|int|\Magento\Eav\Model\Entity\Type $entityType + * @param string $code * @return $this * @throws LocalizedException */ @@ -296,7 +297,7 @@ public function setAttributeId($data) } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getAttributeId() @@ -305,6 +306,8 @@ public function getAttributeId() } /** + * Set attribute code + * * @param string $data * @return $this * @codeCoverageIgnore @@ -315,7 +318,7 @@ public function setAttributeCode($data) } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getAttributeCode() @@ -324,6 +327,8 @@ public function getAttributeCode() } /** + * Set attribute model + * * @param array $data * @return $this * @codeCoverageIgnore @@ -334,6 +339,8 @@ public function setAttributeModel($data) } /** + * Returns attribute model + * * @return array * @codeCoverageIgnore */ @@ -343,6 +350,8 @@ public function getAttributeModel() } /** + * Set backend type + * * @param string $data * @return $this * @codeCoverageIgnore @@ -353,7 +362,7 @@ public function setBackendType($data) } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getBackendType() @@ -362,6 +371,8 @@ public function getBackendType() } /** + * Set backend model + * * @param string $data * @return $this * @codeCoverageIgnore @@ -372,7 +383,7 @@ public function setBackendModel($data) } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getBackendModel() @@ -381,6 +392,8 @@ public function getBackendModel() } /** + * Set backend table + * * @param string $data * @return $this * @codeCoverageIgnore @@ -391,6 +404,8 @@ public function setBackendTable($data) } /** + * Returns is visible on front + * * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) * @codeCoverageIgnore @@ -401,6 +416,8 @@ public function getIsVisibleOnFront() } /** + * Returns default value + * * @return string|int|bool|float * @codeCoverageIgnore */ @@ -422,6 +439,8 @@ public function setDefaultValue($defaultValue) } /** + * Returns attribute set id + * * @return int * @codeCoverageIgnore */ @@ -431,6 +450,8 @@ public function getAttributeSetId() } /** + * Set attribute set id + * * @param int $id * @return $this * @codeCoverageIgnore @@ -443,7 +464,7 @@ public function setAttributeSetId($id) } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function getEntityTypeId() @@ -452,6 +473,8 @@ public function getEntityTypeId() } /** + * Set entity type id + * * @param int|string $id * @return $this * @codeCoverageIgnore @@ -464,6 +487,8 @@ public function setEntityTypeId($id) } /** + * Set entity type + * * @param string $type * @return $this * @codeCoverageIgnore @@ -495,7 +520,7 @@ public function getAlias($entity = null) /** * Set attribute name * - * @param string $name + * @param string $name * @return $this * @codeCoverageIgnore */ @@ -608,9 +633,11 @@ public function getSource() { if (empty($this->_source)) { if (!$this->getSourceModel()) { - $this->setSourceModel($this->_getDefaultSourceModel()); + $this->_source = $this->_getDefaultSourceModel(); + } else { + $this->_source = $this->getSourceModel(); } - $source = $this->_universalFactory->create($this->getSourceModel()); + $source = $this->_universalFactory->create($this->_source); if (!$source) { throw new LocalizedException( __( @@ -639,6 +666,8 @@ public function usesSource() } /** + * Returns default backend model + * * @return string * @codeCoverageIgnore */ @@ -648,6 +677,8 @@ protected function _getDefaultBackendModel() } /** + * Returns default frontend model + * * @return string * @codeCoverageIgnore */ @@ -657,6 +688,8 @@ protected function _getDefaultFrontendModel() } /** + * Returns default source model + * * @return string * @codeCoverageIgnore */ @@ -891,6 +924,7 @@ public function _getFlatColumnsDdlDefinition() /** * Retrieve flat columns definition in old format (before MMDB support) + * * Used in database compatible mode * * @deprecated 100.2.0 @@ -1066,8 +1100,8 @@ public function getFlatUpdateSelect($store = null) } /** + * @inheritdoc * @codeCoverageIgnoreStart - * {@inheritdoc} */ public function getIsUnique() { @@ -1086,7 +1120,7 @@ public function setIsUnique($isUnique) } /** - * {@inheritdoc} + * @inheritdoc */ public function getFrontendClass() { @@ -1105,7 +1139,7 @@ public function setFrontendClass($frontendClass) } /** - * {@inheritdoc} + * @inheritdoc */ public function getFrontendInput() { @@ -1113,7 +1147,7 @@ public function getFrontendInput() } /** - * {@inheritdoc} + * @inheritdoc */ public function setFrontendInput($frontendInput) { @@ -1121,7 +1155,7 @@ public function setFrontendInput($frontendInput) } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsRequired() { @@ -1129,7 +1163,7 @@ public function getIsRequired() } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsRequired($isRequired) { @@ -1139,7 +1173,7 @@ public function setIsRequired($isRequired) //@codeCoverageIgnoreEnd /** - * {@inheritdoc} + * @inheritdoc */ public function getOptions() { @@ -1200,8 +1234,8 @@ protected function convertToObjects(array $options) } /** + * @inheritdoc * @codeCoverageIgnoreStart - * {@inheritdoc} */ public function getIsUserDefined() { @@ -1220,7 +1254,7 @@ public function setIsUserDefined($isUserDefined) } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefaultFrontendLabel() { @@ -1239,7 +1273,7 @@ public function setDefaultFrontendLabel($defaultFrontendLabel) } /** - * {@inheritdoc} + * @inheritdoc */ public function getFrontendLabels() { @@ -1271,7 +1305,7 @@ public function setFrontendLabels(array $frontendLabels = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getNote() { @@ -1290,7 +1324,7 @@ public function setNote($note) } /** - * {@inheritdoc} + * @inheritdoc */ public function getSourceModel() { @@ -1311,7 +1345,7 @@ public function setSourceModel($sourceModel) //@codeCoverageIgnoreEnd /** - * {@inheritdoc} + * @inheritdoc */ public function getValidationRules() { @@ -1338,7 +1372,7 @@ public function setValidationRules(array $validationRules = null) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Eav\Api\Data\AttributeExtensionInterface|null * @codeCoverageIgnore @@ -1355,7 +1389,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Eav\Api\Data\AttributeExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index a2c9611d26f..4e4e146208a 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -10,6 +10,9 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\StateException; +/** + * Eav Option Management + */ class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInterface { /** @@ -36,7 +39,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function add($entityType, $attributeCode, $option) { @@ -79,7 +82,7 @@ public function add($entityType, $attributeCode, $option) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete($entityType, $attributeCode, $optionId) { @@ -110,7 +113,7 @@ public function delete($entityType, $attributeCode, $optionId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getItems($entityType, $attributeCode) { @@ -129,6 +132,8 @@ public function getItems($entityType, $attributeCode) } /** + * Validate option + * * @param \Magento\Eav\Api\Data\AttributeInterface $attribute * @param int $optionId * @throws NoSuchEntityException @@ -148,15 +153,19 @@ protected function validateOption($attribute, $optionId) } /** + * Returns option id + * * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option * @return string */ private function getOptionId(\Magento\Eav\Api\Data\AttributeOptionInterface $option) : string { - return $option->getValue() ?: 'new_option'; + return 'id_' . ($option->getValue() ?: 'new_option'); } /** + * Set option value + * * @param \Magento\Eav\Api\Data\AttributeOptionInterface $option * @param \Magento\Eav\Api\Data\AttributeInterface $attribute * @param string $optionLabel diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php b/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php index 8f8c39bf260..f9aa1a9ed3b 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php @@ -9,6 +9,8 @@ use Magento\Store\Model\StoreManagerInterface; /** + * Eav attribute default source when values are coming from another table + * * @api * @since 100.0.2 */ @@ -127,12 +129,14 @@ public function getSpecificOptions($ids, $withEmpty = true) } /** + * Add an empty option to the array + * * @param array $options * @return array */ private function addEmptyOption(array $options) { - array_unshift($options, ['label' => $this->getAttribute()->getIsRequired() ? '' : ' ', 'value' => '']); + array_unshift($options, ['label' => ' ', 'value' => '']); return $options; } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php index 2ae31a05d95..b63a4dd2c9a 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php @@ -59,13 +59,13 @@ public function testAdd() $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class); $option = ['value' => [ - 'new_option' => [ + 'id_new_option' => [ 0 => 'optionLabel', 42 => 'labelLabel', ], ], 'order' => [ - 'new_option' => 'optionSortOrder', + 'id_new_option' => 'optionSortOrder', ], ]; @@ -78,10 +78,10 @@ public function testAdd() $labelMock->expects($this->once())->method('getStoreId')->willReturn(42); $labelMock->expects($this->once())->method('getLabel')->willReturn('labelLabel'); $optionMock->expects($this->once())->method('getIsDefault')->willReturn(true); - $attributeMock->expects($this->once())->method('setDefault')->with(['new_option']); + $attributeMock->expects($this->once())->method('setDefault')->with(['id_new_option']); $attributeMock->expects($this->once())->method('setOption')->with($option); $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock); - $this->assertEquals('new_option', $this->model->add($entityType, $attributeCode, $optionMock)); + $this->assertEquals('id_new_option', $this->model->add($entityType, $attributeCode, $optionMock)); } /** @@ -167,13 +167,13 @@ public function testAddWithCannotSaveException() $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class); $option = ['value' => [ - 'new_option' => [ + 'id_new_option' => [ 0 => 'optionLabel', 42 => 'labelLabel', ], ], 'order' => [ - 'new_option' => 'optionSortOrder', + 'id_new_option' => 'optionSortOrder', ], ]; @@ -186,7 +186,7 @@ public function testAddWithCannotSaveException() $labelMock->expects($this->once())->method('getStoreId')->willReturn(42); $labelMock->expects($this->once())->method('getLabel')->willReturn('labelLabel'); $optionMock->expects($this->once())->method('getIsDefault')->willReturn(true); - $attributeMock->expects($this->once())->method('setDefault')->with(['new_option']); + $attributeMock->expects($this->once())->method('setDefault')->with(['id_new_option']); $attributeMock->expects($this->once())->method('setOption')->with($option); $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock) ->willThrowException(new \Exception()); diff --git a/app/code/Magento/Eav/etc/db_schema.xml b/app/code/Magento/Eav/etc/db_schema.xml index f930321e525..e81fad5ebde 100644 --- a/app/code/Magento/Eav/etc/db_schema.xml +++ b/app/code/Magento/Eav/etc/db_schema.xml @@ -15,7 +15,7 @@ <column xsi:type="varchar" name="attribute_model" nullable="true" length="255" comment="Attribute Model"/> <column xsi:type="varchar" name="entity_table" nullable="true" length="255" comment="Entity Table"/> <column xsi:type="varchar" name="value_table_prefix" nullable="true" length="255" comment="Value Table Prefix"/> - <column xsi:type="varchar" name="entity_id_field" nullable="true" length="255" comment="Entity Id Field"/> + <column xsi:type="varchar" name="entity_id_field" nullable="true" length="255" comment="Entity ID Field"/> <column xsi:type="smallint" name="is_data_sharing" padding="5" unsigned="true" nullable="false" identity="false" default="1" comment="Defines Is Data Sharing"/> <column xsi:type="varchar" name="data_sharing_key" nullable="true" length="100" default="default" @@ -42,7 +42,7 @@ </table> <table name="eav_entity" resource="default" engine="innodb" comment="Eav Entity"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="entity_type_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Entity Type Id"/> <column xsi:type="smallint" name="attribute_set_id" padding="5" unsigned="true" nullable="false" @@ -83,7 +83,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="datetime" name="value" on_update="false" nullable="true" comment="Attribute Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -123,7 +123,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Attribute Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -164,7 +164,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="int" name="value" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Attribute Value"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -204,7 +204,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="text" name="value" nullable="false" comment="Attribute Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> @@ -241,7 +241,7 @@ <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Store Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="value" nullable="true" length="255" comment="Attribute Value"/> <constraint xsi:type="primary" name="PRIMARY"> <column name="value_id"/> diff --git a/app/code/Magento/Elasticsearch/etc/indexer.xml b/app/code/Magento/Elasticsearch/etc/indexer.xml index 8d75a59f840..f22eb8f0bd3 100644 --- a/app/code/Magento/Elasticsearch/etc/indexer.xml +++ b/app/code/Magento/Elasticsearch/etc/indexer.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd"> <indexer id="catalogsearch_fulltext"> <dependencies> + <indexer id="catalog_category_product" /> <indexer id="cataloginventory_stock" /> <indexer id="catalog_product_price" /> </dependencies> diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Edit.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Edit.php index 240b688402b..998cf62a83a 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Edit.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Email\Controller\Adminhtml\Email\Template; -class Edit extends \Magento\Email\Controller\Adminhtml\Email\Template +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Email\Controller\Adminhtml\Email\Template implements HttpGetActionInterface { /** * Edit transactional email action diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Index.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Index.php index 11b38ae8e50..013f97b9ad3 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Index.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Email\Controller\Adminhtml\Email\Template; -class Index extends \Magento\Email\Controller\Adminhtml\Email\Template +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Email\Controller\Adminhtml\Email\Template implements HttpGetActionInterface { /** * Index action diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/NewAction.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/NewAction.php index f5024943e83..87f5470440d 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/NewAction.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Email\Controller\Adminhtml\Email\Template; -class NewAction extends \Magento\Email\Controller\Adminhtml\Email\Template +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Email\Controller\Adminhtml\Email\Template implements HttpGetActionInterface { /** * New transactional email action diff --git a/app/code/Magento/Email/view/frontend/email/header.html b/app/code/Magento/Email/view/frontend/email/header.html index a6895f629b6..c4f49698dc6 100644 --- a/app/code/Magento/Email/view/frontend/email/header.html +++ b/app/code/Magento/Email/view/frontend/email/header.html @@ -43,8 +43,6 @@ {{if logo_height}} height="{{var logo_height}}" - {{else}} - height="52" {{/if}} src="{{var logo_url}}" diff --git a/app/code/Magento/Email/view/frontend/web/logo_email.png b/app/code/Magento/Email/view/frontend/web/logo_email.png index d01b530456e..215e9d06edc 100644 Binary files a/app/code/Magento/Email/view/frontend/web/logo_email.png and b/app/code/Magento/Email/view/frontend/web/logo_email.png differ diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php index 8e42e0c1313..86fc0082f7a 100644 --- a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php @@ -6,10 +6,12 @@ */ namespace Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + /** * Key Index action */ -class Index extends \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key +class Index extends \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key implements HttpGetActionInterface { /** * Render main page with form diff --git a/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml b/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml index 892a22129ad..c3f34c5abc1 100644 --- a/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml +++ b/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml @@ -7,8 +7,8 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> - <referenceContainer name="head.additional"> + <referenceBlock name="head.additional"> <block class="Magento\GoogleAnalytics\Block\Ga" name="google_analytics" as="google_analytics" template="Magento_GoogleAnalytics::ga.phtml"/> - </referenceContainer> + </referenceBlock> </body> </page> diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index c6651cdde0c..ce2166808b4 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -29,6 +29,7 @@ input FilterTypeInput @doc(description: "FilterTypeInput specifies which action type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navigation for the query response") { page_size: Int @doc(description: "Specifies the maximum number of items to return") current_page: Int @doc(description: "Specifies which page of results to return") + total_pages: Int @doc(description: "Total pages") } enum SortEnum @doc(description: "This enumeration indicates whether to return results in ascending or descending order") { diff --git a/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml b/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml index 34de1a18cf2..d9238e9794d 100644 --- a/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml @@ -26,7 +26,7 @@ if ($minProduct) { ); } ?> -<div class="price-box" itemprop="offers" itemscope itemtype="http://schema.org/Offer"> +<div class="price-box"> <?php if ($minProduct && \Magento\Framework\Pricing\Render::ZONE_ITEM_VIEW != $block->getZone()): ?> <p class="minimal-price"> <span class="price-label"><?= /* @escapeNotVerified */ __('Starting at') ?></span><?= $amountRender->toHtml() ?> diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php index 06d8610a247..38bfbd88b0c 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php @@ -5,6 +5,7 @@ */ namespace Magento\ImportExport\Controller\Adminhtml\Export; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Backend\App\Action\Context; @@ -13,7 +14,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; -class Export extends ExportController +class Export extends ExportController implements HttpPostActionInterface { /** * @var \Magento\Framework\App\Response\Http\FileFactory diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php index 05475b51619..722d32c9eb2 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php @@ -5,10 +5,12 @@ */ namespace Magento\ImportExport\Controller\Adminhtml\Export; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Controller\ResultFactory; -class GetFilter extends ExportController +class GetFilter extends ExportController implements HttpGetActionInterface, HttpPostActionInterface { /** * Get grid-filter of entity attributes action. diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Index.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Index.php index d2ca99f1a79..3dd5bfd550a 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Index.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Index.php @@ -5,10 +5,11 @@ */ namespace Magento\ImportExport\Controller\Adminhtml\Export; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Controller\ResultFactory; -class Index extends ExportController +class Index extends ExportController implements HttpGetActionInterface { /** * Index action. diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Index.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Index.php index df32d61cceb..3cca7ae7ccc 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Index.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Index.php @@ -5,10 +5,11 @@ */ namespace Magento\ImportExport\Controller\Adminhtml\Import; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\ImportExport\Controller\Adminhtml\Import as ImportController; use Magento\Framework\Controller\ResultFactory; -class Index extends ImportController +class Index extends ImportController implements HttpGetActionInterface { /** * Index action diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Start.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Start.php index 695a0e61709..8896f84ce24 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Start.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Start.php @@ -5,10 +5,11 @@ */ namespace Magento\ImportExport\Controller\Adminhtml\Import; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\ImportExport\Controller\Adminhtml\ImportResult as ImportResultController; use Magento\Framework\Controller\ResultFactory; -class Start extends ImportResultController +class Start extends ImportResultController implements HttpPostActionInterface { /** * @var \Magento\ImportExport\Model\Import diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php index df9f63e79b7..204e9b11085 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php @@ -5,15 +5,15 @@ */ namespace Magento\ImportExport\Controller\Adminhtml\Import; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\ImportExport\Controller\Adminhtml\ImportResult as ImportResultController; use Magento\ImportExport\Model\Import; use Magento\ImportExport\Block\Adminhtml\Import\Frame\Result; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Model\Import\Adapter as ImportAdapter; -use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; -class Validate extends ImportResultController +class Validate extends ImportResultController implements HttpPostActionInterface { /** * @var Import @@ -161,7 +161,7 @@ private function addMessageForValidResult(Result $resultBlock) /** * Collect errors and add error messages to Result block * - * Get all errors from ProcessingErrorAggregatorInterface and add appropriated error messages + * Get all errors from Error Aggregator and add appropriated error messages * to Result block. * * @param Result $resultBlock diff --git a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/ListAction.php b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/ListAction.php index 02e600dbd93..35cefbcda43 100644 --- a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/ListAction.php +++ b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/ListAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Indexer\Controller\Adminhtml\Indexer; -class ListAction extends \Magento\Indexer\Controller\Adminhtml\Indexer +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class ListAction extends \Magento\Indexer\Controller\Adminhtml\Indexer implements HttpGetActionInterface { /** * Display processes grid action diff --git a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php index b4a4d9f06ae..3dffd514218 100644 --- a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php +++ b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php @@ -6,7 +6,9 @@ */ namespace Magento\Indexer\Controller\Adminhtml\Indexer; -class MassChangelog extends \Magento\Indexer\Controller\Adminhtml\Indexer +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class MassChangelog extends \Magento\Indexer\Controller\Adminhtml\Indexer implements HttpPostActionInterface { /** * Turn mview on for the given indexers diff --git a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php index 7ace4a64d38..9f7faff8843 100644 --- a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php +++ b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php @@ -6,7 +6,9 @@ */ namespace Magento\Indexer\Controller\Adminhtml\Indexer; -class MassOnTheFly extends \Magento\Indexer\Controller\Adminhtml\Indexer +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class MassOnTheFly extends \Magento\Indexer\Controller\Adminhtml\Indexer implements HttpPostActionInterface { /** * Turn mview off for the given indexers diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php index 36073af5632..4ce462bb44c 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php @@ -6,11 +6,12 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Integration\Block\Adminhtml\Integration\Edit\Tab\Info; use Magento\Framework\Exception\IntegrationException; use Magento\Framework\Controller\ResultFactory; -class Delete extends \Magento\Integration\Controller\Adminhtml\Integration +class Delete extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpPostActionInterface { /** * Delete the integration. diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php index 97cd7b46e08..599b6017059 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php @@ -6,11 +6,12 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Integration\Block\Adminhtml\Integration\Edit\Tab\Info; use Magento\Framework\Exception\IntegrationException; -class Edit extends \Magento\Integration\Controller\Adminhtml\Integration +class Edit extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpGetActionInterface { /** * Edit integration action. diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Grid.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Grid.php index bd10873a59f..f3ad4306db5 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Grid.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Grid.php @@ -6,7 +6,11 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; -class Grid extends \Magento\Integration\Controller\Adminhtml\Integration +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Integration\Controller\Adminhtml\Integration as IntegrationAction; + +class Grid extends IntegrationAction implements HttpPostActionInterface, HttpGetActionInterface { /** * AJAX integrations grid. diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Index.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Index.php index 2caf9054ce3..131e1e14947 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Index.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; -class Index extends \Magento\Integration\Controller\Adminhtml\Integration +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpGetActionInterface { /** * Integrations grid. diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/NewAction.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/NewAction.php index e05d559aa1c..7354b22ccf4 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/NewAction.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; -class NewAction extends \Magento\Integration\Controller\Adminhtml\Integration +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpGetActionInterface { /** * New integration action. diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php index 8c2e10ebf1f..8b2a94da01d 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php @@ -6,9 +6,10 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Exception\IntegrationException; -class PermissionsDialog extends \Magento\Integration\Controller\Adminhtml\Integration +class PermissionsDialog extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpGetActionInterface { /** * Show permissions popup. diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php index cb2fcb2ba0e..8bcbb456534 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php @@ -5,6 +5,7 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Integration\Block\Adminhtml\Integration\Edit\Tab\Info; use Magento\Framework\Exception\IntegrationException; use Magento\Framework\Exception\LocalizedException; @@ -17,7 +18,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Integration\Controller\Adminhtml\Integration +class Save extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpPostActionInterface { /** * @var SecurityCookie diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php index dcea6da3212..4c99dafb1d9 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php @@ -6,9 +6,10 @@ */ namespace Magento\Integration\Controller\Adminhtml\Integration; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Integration\Model\Integration as IntegrationModel; -class TokensDialog extends \Magento\Integration\Controller\Adminhtml\Integration +class TokensDialog extends \Magento\Integration\Controller\Adminhtml\Integration implements HttpGetActionInterface { /** * Set success message based on Integration activation or re-authorization. diff --git a/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php b/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php index 674a97a68d0..c10759d6027 100644 --- a/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php +++ b/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php @@ -36,7 +36,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function convert($source) { @@ -80,9 +80,16 @@ public function convert($source) $result[$integrationName][self::API_RESOURCES][] = $name; } } + + // Add root resource if any child has been added + if (!empty($result[$integrationName][self::API_RESOURCES])) { + array_unshift($result[$integrationName][self::API_RESOURCES], $allResources[1]['id']); + } + // Remove any duplicates added parents - $result[$integrationName][self::API_RESOURCES] = - array_values(array_unique($result[$integrationName][self::API_RESOURCES])); + $result[$integrationName][self::API_RESOURCES] = array_values( + array_unique($result[$integrationName][self::API_RESOURCES]) + ); } return $result; } diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php index 5b7bbace5a6..42b62acaba5 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php @@ -6,6 +6,8 @@ return [ [], [ + 'id' => 'Magento_Backend::admin', + 'title' => 'Magento Admin (Root)', 'children' => [ [ diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php index 54e05d5ef90..0293492c776 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php @@ -9,6 +9,7 @@ 'endpoint_url' => 'http://endpoint.com', 'identity_link_url' => 'http://www.example.com/identity', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Customer::manageParent', 'Magento_Customer::manage', 'Magento_SalesRule::quoteParent', @@ -17,6 +18,9 @@ ], 'TestIntegration2' => [ 'email' => 'test-integration2@magento.com', - 'resource' => ['Magento_Sales::sales'] + 'resource' => [ + 'Magento_Backend::admin', + 'Magento_Sales::sales' + ] ] ]; diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml index 585fabc2429..f8bcf3fc4a2 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml @@ -18,6 +18,7 @@ <integration name="TestIntegration2"> <email>test-integration2@magento.com</email> <resources> + <resource name="Magento_Backend::admin" /> <resource name="Magento_Sales::sales" /> </resources> </integration> diff --git a/app/code/Magento/Integration/etc/db_schema.xml b/app/code/Magento/Integration/etc/db_schema.xml index 9e81b867d36..33173df4f6c 100644 --- a/app/code/Magento/Integration/etc/db_schema.xml +++ b/app/code/Magento/Integration/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="oauth_consumer" resource="default" engine="innodb" comment="OAuth Consumers"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="true" default="0" diff --git a/app/code/Magento/Integration/view/adminhtml/templates/integration/popup_container.phtml b/app/code/Magento/Integration/view/adminhtml/templates/integration/popup_container.phtml index 8b7e787337e..ae16da1760f 100644 --- a/app/code/Magento/Integration/view/adminhtml/templates/integration/popup_container.phtml +++ b/app/code/Magento/Integration/view/adminhtml/templates/integration/popup_container.phtml @@ -15,7 +15,8 @@ "jquery", 'Magento_Ui/js/modal/confirm', "jquery/ui", - "Magento_Integration/js/integration" + "Magento_Integration/js/integration", + 'mage/dataPost' ], function ($, Confirm) { window.integration = new Integration( @@ -37,7 +38,7 @@ content: "<?= /* @escapeNotVerified */ __("Are you sure you want to delete this integration? You can't undo this action.") ?>", actions: { confirm: function () { - window.location.href = $(e.target).data('url'); + $.mage.dataPost().postData({action: $(e.target).data('url'), data: {}}); } } }); diff --git a/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php b/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php index 6b64610caf5..1bb601e3a4e 100644 --- a/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php +++ b/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php @@ -11,6 +11,9 @@ use Magento\Framework\Module\Manager; use Magento\Framework\Event\ObserverInterface; +/** + * Observer for Product Attribute Form + */ class ProductAttributeFormBuildFrontTabObserver implements ObserverInterface { /** @@ -34,6 +37,8 @@ public function __construct(Manager $moduleManager, Source\Yesno $optionList) } /** + * Execute + * * @param \Magento\Framework\Event\Observer $observer * @return void */ @@ -54,8 +59,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) [ 'name' => 'is_filterable', 'label' => __("Use in Layered Navigation"), - 'title' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price'), - 'note' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price.'), + 'title' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price'), + 'note' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price.'), 'values' => [ ['value' => '0', 'label' => __('No')], ['value' => '1', 'label' => __('Filterable (with results)')], @@ -70,8 +75,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) [ 'name' => 'is_filterable_in_search', 'label' => __("Use in Search Results Layered Navigation"), - 'title' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price'), - 'note' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price.'), + 'title' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price'), + 'note' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price.'), 'values' => $this->optionList->toOptionArray(), ] ); diff --git a/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml index 000e10197af..0d7476081d1 100644 --- a/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -11,6 +11,7 @@ <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="valuesForEnable" xsi:type="array"> + <item name="boolean" xsi:type="string">boolean</item> <item name="select" xsi:type="string">select</item> <item name="multiselect" xsi:type="string">multiselect</item> <item name="price" xsi:type="string">price</item> @@ -19,7 +20,7 @@ </item> </argument> <settings> - <notice translate="true">Can be used only with catalog input type Dropdown, Multiple Select and Price.</notice> + <notice translate="true">Can be used only with catalog input type Yes/No (Boolean), Dropdown, Multiple Select and Price.</notice> <dataType>string</dataType> <label translate="true">Use in Layered Navigation</label> <dataScope>is_filterable</dataScope> @@ -36,6 +37,7 @@ <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="valuesForEnable" xsi:type="array"> + <item name="boolean" xsi:type="string">boolean</item> <item name="select" xsi:type="string">select</item> <item name="multiselect" xsi:type="string">multiselect</item> <item name="price" xsi:type="string">price</item> @@ -45,7 +47,7 @@ </item> </argument> <settings> - <notice translate="true">Can be used only with catalog input type Dropdown, Multiple Select and Price.</notice> + <notice translate="true">Can be used only with catalog input type Yes/No (Boolean), Dropdown, Multiple Select and Price.</notice> <label translate="true">Use in Search Results Layered Navigation</label> <dataScope>is_filterable_in_search</dataScope> <imports> diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Addresses.php b/app/code/Magento/Multishipping/Controller/Checkout/Addresses.php index 78ea9fc1d2b..f97589342bc 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Addresses.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Addresses.php @@ -6,9 +6,10 @@ */ namespace Magento\Multishipping\Controller\Checkout; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; -class Addresses extends \Magento\Multishipping\Controller\Checkout +class Addresses extends \Magento\Multishipping\Controller\Checkout implements HttpGetActionInterface { /** * Multishipping checkout select address page diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Index.php b/app/code/Magento/Multishipping/Controller/Checkout/Index.php index 1fa96cfab8a..a4314d6d518 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Index.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Multishipping\Controller\Checkout; -class Index extends \Magento\Multishipping\Controller\Checkout +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Multishipping\Controller\Checkout implements HttpGetActionInterface { /** * Index action of Multishipping checkout diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php index 9412b138955..84ee5285a73 100644 --- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php +++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php @@ -176,8 +176,6 @@ class Multishipping extends \Magento\Framework\DataObject private $dataObjectHelper; /** - * Constructor - * * @param \Magento\Checkout\Model\Session $checkoutSession * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Sales\Model\OrderFactory $orderFactory @@ -202,8 +200,9 @@ class Multishipping extends \Magento\Framework\DataObject * @param array $data * @param \Magento\Quote\Api\Data\CartExtensionFactory|null $cartExtensionFactory * @param AllowedCountries|null $allowedCountryReader - * @param Multishipping\PlaceOrderFactory $placeOrderFactory - * @param LoggerInterface $logger + * @param Multishipping\PlaceOrderFactory|null $placeOrderFactory + * @param LoggerInterface|null $logger + * @param \Magento\Framework\Api\DataObjectHelper|null $dataObjectHelper * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -272,6 +271,7 @@ public function __construct( /** * Initialize multishipping checkout. + * * Split virtual/not virtual items between default billing/shipping addresses * * @return \Magento\Multishipping\Model\Checkout\Type\Multishipping @@ -337,6 +337,7 @@ protected function _init() /** * Get quote items assigned to different quote addresses populated per item qty. + * * Based on result array we can display each item separately * * @return array @@ -397,7 +398,7 @@ public function removeAddressItem($addressId, $itemId) /** * Assign quote items to addresses and specify items qty * - * array structure: + * Array structure: * array( * $quoteItemId => array( * 'qty' => $qty, @@ -693,6 +694,7 @@ protected function _prepareOrder(\Magento\Quote\Model\Quote\Address $address) $order->setIsVirtual(1); } else { $order->setShippingAddress($this->quoteAddressToOrderAddress->convert($address)); + $order->setShippingMethod($address->getShippingMethod()); } $order->setPayment($this->quotePaymentToOrderPayment->convert($quote->getPayment())); @@ -927,6 +929,8 @@ public function getMinimumAmountDescription() } /** + * Get minimum amount error. + * * @return string */ public function getMinimumAmountError() @@ -1071,7 +1075,7 @@ public function getCustomer() /** * Check if specified address ID belongs to customer. * - * @param $addressId + * @param mixed $addressId * @return bool */ protected function isAddressIdApplicable($addressId) @@ -1084,6 +1088,8 @@ protected function isAddressIdApplicable($addressId) } /** + * Prepare shipping assignment. + * * @param \Magento\Quote\Model\Quote $quote * @return \Magento\Quote\Model\Quote */ @@ -1104,6 +1110,8 @@ private function prepareShippingAssignment($quote) } /** + * Get shipping assignment processor. + * * @return \Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor */ private function getShippingAssignmentProcessor() @@ -1186,10 +1194,11 @@ private function searchQuoteAddressId(OrderInterface $order, array $addresses): } /** + * Get quote address errors. + * * @param OrderInterface[] $orders * @param \Magento\Quote\Model\Quote\Address[] $addresses * @param \Exception[] $exceptionList - * * @return string[] * @throws NotFoundException */ diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php index 94c11bef1d3..853d3e3046b 100644 --- a/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php +++ b/app/code/Magento/Multishipping/Test/Unit/Model/Checkout/Type/MultishippingTest.php @@ -659,11 +659,13 @@ private function getOrderMock( 'getId', 'getCanSendNewEmailFlag', 'getItems', + 'setShippingMethod', ] )->getMock(); $orderMock->method('setQuote')->with($this->quoteMock); $orderMock->method('setBillingAddress')->with($orderAddressMock)->willReturnSelf(); $orderMock->method('setShippingAddress')->with($orderAddressMock)->willReturnSelf(); + $orderMock->expects($this->once())->method('setShippingMethod')->with('carrier')->willReturnSelf(); $orderMock->method('setPayment')->with($orderPaymentMock)->willReturnSelf(); $orderMock->method('addItem')->with($orderItemMock)->willReturnSelf(); $orderMock->method('getIncrementId')->willReturn('1'); diff --git a/app/code/Magento/NewRelicReporting/etc/db_schema.xml b/app/code/Magento/NewRelicReporting/etc/db_schema.xml index 6f9ce29436f..f6d463de0ce 100644 --- a/app/code/Magento/NewRelicReporting/etc/db_schema.xml +++ b/app/code/Magento/NewRelicReporting/etc/db_schema.xml @@ -10,7 +10,7 @@ <table name="reporting_counts" resource="default" engine="innodb" comment="Reporting for all count related events generated via the cron job"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="type" nullable="true" length="255" comment="Item Reported"/> <column xsi:type="int" name="count" padding="10" unsigned="true" nullable="true" identity="false" comment="Count Value"/> @@ -35,9 +35,9 @@ </table> <table name="reporting_orders" resource="default" engine="innodb" comment="Reporting for all orders"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" - comment="Customer Id"/> + comment="Customer ID"/> <column xsi:type="decimal" name="total" scale="0" precision="10" unsigned="true" nullable="true"/> <column xsi:type="decimal" name="total_base" scale="0" precision="10" unsigned="true" nullable="true"/> <column xsi:type="int" name="item_count" padding="10" unsigned="true" nullable="false" identity="false" @@ -50,7 +50,7 @@ </table> <table name="reporting_users" resource="default" engine="innodb" comment="Reporting for user actions"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="type" nullable="true" length="255" comment="User Type"/> <column xsi:type="varchar" name="action" nullable="true" length="255" comment="Action Performed"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" @@ -61,7 +61,7 @@ </table> <table name="reporting_system_updates" resource="default" engine="innodb" comment="Reporting for system updates"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="type" nullable="true" length="255" comment="Update Type"/> <column xsi:type="varchar" name="action" nullable="true" length="255" comment="Action Performed"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Problem/Index.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Problem/Index.php index 7ea3b2cb909..453a42def61 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Problem/Index.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Problem/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Problem; -class Index extends \Magento\Newsletter\Controller\Adminhtml\Problem +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Newsletter\Controller\Adminhtml\Problem implements HttpGetActionInterface { /** * Newsletter problems report page diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Edit.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Edit.php index b012979a11a..f99c22a4743 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Edit.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Queue; -class Edit extends \Magento\Newsletter\Controller\Adminhtml\Queue +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Newsletter\Controller\Adminhtml\Queue implements HttpGetActionInterface { /** * Core registry diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Grid.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Grid.php index c992fe6c377..4f734c09e47 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Grid.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Grid.php @@ -6,7 +6,11 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Queue; -class Grid extends \Magento\Newsletter\Controller\Adminhtml\Queue +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Newsletter\Controller\Adminhtml\Queue as QueueAction; + +class Grid extends QueueAction implements HttpPostActionInterface, HttpGetActionInterface { /** * Queue list Ajax action diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Index.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Index.php index 22c7adc5255..2d07ffabe38 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Index.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Index.php @@ -6,7 +6,14 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Queue; -class Index extends \Magento\Newsletter\Controller\Adminhtml\Queue +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Newsletter\Controller\Adminhtml\Queue as QueueAction; + +/** + * Show newsletter queue. Needs to be accessible by POST because of filtering. + */ +class Index extends QueueAction implements HttpGetActionInterface, HttpPostActionInterface { /** * Queue list action diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php index d3b091976e9..2dbe10bf1bd 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php @@ -7,7 +7,9 @@ namespace Magento\Newsletter\Controller\Adminhtml\Queue; -class Save extends \Magento\Newsletter\Controller\Adminhtml\Queue +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Save extends \Magento\Newsletter\Controller\Adminhtml\Queue implements HttpPostActionInterface { /** * Save Newsletter queue diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/Index.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/Index.php index a730b8a21a1..09cf1ae7959 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/Index.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/Index.php @@ -6,7 +6,11 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Subscriber; -class Index extends \Magento\Newsletter\Controller\Adminhtml\Subscriber +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Newsletter\Controller\Adminhtml\Subscriber as SubscriberAction; + +class Index extends SubscriberAction implements HttpGetActionInterface, HttpPostActionInterface { /** * Newsletter subscribers page diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Drop.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Drop.php index 52d46065ad0..2d167d4ccf5 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Drop.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Drop.php @@ -6,7 +6,9 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Template; -class Drop extends \Magento\Newsletter\Controller\Adminhtml\Template +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Drop extends \Magento\Newsletter\Controller\Adminhtml\Template implements HttpPostActionInterface { /** * Drop Newsletter Template diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Edit.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Edit.php index a1126be35af..e60b865003f 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Edit.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Template; -class Edit extends \Magento\Newsletter\Controller\Adminhtml\Template +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\Newsletter\Controller\Adminhtml\Template implements HttpGetActionInterface { /** * Core registry diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Index.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Index.php index 5fcce53c6ab..12cdb7e262d 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Index.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Template; -class Index extends \Magento\Newsletter\Controller\Adminhtml\Template +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Newsletter\Controller\Adminhtml\Template implements HttpGetActionInterface { /** * View Templates list diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/NewAction.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/NewAction.php index 054e9025a4f..c739d6ba26b 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/NewAction.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Template; -class NewAction extends \Magento\Newsletter\Controller\Adminhtml\Template +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\Newsletter\Controller\Adminhtml\Template implements HttpGetActionInterface { /** * Create new Newsletter Template diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Preview.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Preview.php index fbce319a75f..9fd9f4335b5 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Preview.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Preview.php @@ -6,7 +6,12 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Template; -class Preview extends \Magento\Newsletter\Controller\Adminhtml\Template +use Magento\Framework\App\Action\HttpGetActionInterface; + +/** + * View a rendered template. + */ +class Preview extends \Magento\Newsletter\Controller\Adminhtml\Template implements HttpGetActionInterface { /** * Preview Newsletter template diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php index c9db0d62e2e..8fc729ea340 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php @@ -6,10 +6,11 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Template; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\TemplateTypesInterface; use Magento\Framework\Exception\LocalizedException; -class Save extends \Magento\Newsletter\Controller\Adminhtml\Template +class Save extends \Magento\Newsletter\Controller\Adminhtml\Template implements HttpPostActionInterface { /** * Save Newsletter Template diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php index efc469e15de..88fa1281627 100644 --- a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php +++ b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php @@ -1,16 +1,21 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Newsletter\Controller\Subscriber; -class Unsubscribe extends \Magento\Newsletter\Controller\Subscriber +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +/** + * Controller for unsubscribing customers. + */ +class Unsubscribe extends \Magento\Newsletter\Controller\Subscriber implements HttpGetActionInterface { /** - * Unsubscribe newsletter - * @return void + * Unsubscribe newsletter. + * + * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() { @@ -27,6 +32,9 @@ public function execute() $this->messageManager->addException($e, __('Something went wrong while unsubscribing you.')); } } - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); + /** @var \Magento\Backend\Model\View\Result\Redirect $redirect */ + $redirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT); + $redirectUrl = $this->_redirect->getRedirectUrl(); + return $redirect->setUrl($redirectUrl); } } diff --git a/app/code/Magento/Newsletter/view/adminhtml/templates/template/edit.phtml b/app/code/Magento/Newsletter/view/adminhtml/templates/template/edit.phtml index 7355c06bdd6..279afe2c28e 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/templates/template/edit.phtml +++ b/app/code/Magento/Newsletter/view/adminhtml/templates/template/edit.phtml @@ -19,8 +19,7 @@ use Magento\Framework\App\TemplateTypesInterface; </div> <?= /* @noEscape */ $block->getForm() ?> </form> -<form action="<?= $block->escapeUrl($block->getPreviewUrl()) ?>" method="post" id="newsletter_template_preview_form" target="_blank"> - <?= $block->getBlockHtml('formkey') ?> +<form action="<?= $block->escapeUrl($block->getPreviewUrl()) ?>" method="get" id="newsletter_template_preview_form" target="_blank"> <div class="no-display"> <input type="hidden" id="preview_type" name="type" value="<?= /* @noEscape */ $block->isTextType() ? 1 : 2 ?>" /> <input type="hidden" id="preview_text" name="text" value="" /> diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index 72fd8c3a63f..bb81f9ebb47 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -61,6 +61,7 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $resultMethodFactory * @param \Magento\OfflineShipping\Model\ResourceModel\Carrier\TablerateFactory $tablerateFactory * @param array $data + * @throws LocalizedException * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function __construct( diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php index 9431973fdfe..f7b487d37bf 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php @@ -9,6 +9,9 @@ use Magento\Framework\Phrase; use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\LocationDirectory; +/** + * Row parser. + */ class RowParser { /** @@ -26,6 +29,8 @@ public function __construct(LocationDirectory $locationDirectory) } /** + * Retrieve columns. + * * @return array */ public function getColumns() @@ -42,6 +47,8 @@ public function getColumns() } /** + * Parse provided row data. + * * @param array $rowData * @param int $rowNumber * @param int $websiteId @@ -71,23 +78,30 @@ public function parse( } $countryId = $this->getCountryId($rowData, $rowNumber, $columnResolver); - $regionId = $this->getRegionId($rowData, $rowNumber, $columnResolver, $countryId); + $regionIds = $this->getRegionIds($rowData, $rowNumber, $columnResolver, $countryId); $zipCode = $this->getZipCode($rowData, $columnResolver); $conditionValue = $this->getConditionValue($rowData, $rowNumber, $conditionFullName, $columnResolver); $price = $this->getPrice($rowData, $rowNumber, $columnResolver); - return [ - 'website_id' => $websiteId, - 'dest_country_id' => $countryId, - 'dest_region_id' => $regionId, - 'dest_zip' => $zipCode, - 'condition_name' => $conditionShortName, - 'condition_value' => $conditionValue, - 'price' => $price, - ]; + $rates = []; + foreach ($regionIds as $regionId) { + $rates[] = [ + 'website_id' => $websiteId, + 'dest_country_id' => $countryId, + 'dest_region_id' => $regionId, + 'dest_zip' => $zipCode, + 'condition_name' => $conditionShortName, + 'condition_value' => $conditionValue, + 'price' => $price, + ]; + } + + return $rates; } /** + * Get country id from provided row data. + * * @param array $rowData * @param int $rowNumber * @param ColumnResolver $columnResolver @@ -116,21 +130,23 @@ private function getCountryId(array $rowData, $rowNumber, ColumnResolver $column } /** + * Retrieve region id from provided row data. + * * @param array $rowData * @param int $rowNumber * @param ColumnResolver $columnResolver * @param int $countryId - * @return int|string + * @return array * @throws ColumnNotFoundException * @throws RowException */ - private function getRegionId(array $rowData, $rowNumber, ColumnResolver $columnResolver, $countryId) + private function getRegionIds(array $rowData, $rowNumber, ColumnResolver $columnResolver, $countryId) { $regionCode = $columnResolver->getColumnValue(ColumnResolver::COLUMN_REGION, $rowData); if ($countryId !== '0' && $this->locationDirectory->hasRegionId($countryId, $regionCode)) { - $regionId = $this->locationDirectory->getRegionId($countryId, $regionCode); + $regionIds = $this->locationDirectory->getRegionIds($countryId, $regionCode); } elseif ($regionCode === '*' || $regionCode === '') { - $regionId = 0; + $regionIds = [0]; } else { throw new RowException( __( @@ -141,10 +157,12 @@ private function getRegionId(array $rowData, $rowNumber, ColumnResolver $columnR ) ); } - return $regionId; + return $regionIds; } /** + * Retrieve zip code from provided row data. + * * @param array $rowData * @param ColumnResolver $columnResolver * @return float|int|null|string @@ -160,6 +178,8 @@ private function getZipCode(array $rowData, ColumnResolver $columnResolver) } /** + * Get condition value form provided row data. + * * @param array $rowData * @param int $rowNumber * @param string $conditionFullName @@ -187,6 +207,8 @@ private function getConditionValue(array $rowData, $rowNumber, $conditionFullNam } /** + * Retrieve price from provided row data. + * * @param array $rowData * @param int $rowNumber * @param ColumnResolver $columnResolver @@ -212,6 +234,7 @@ private function getPrice(array $rowData, $rowNumber, ColumnResolver $columnReso /** * Parse and validate positive decimal value + * * Return false if value is not decimal or is not positive * * @param string $value diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php index a5b0d7e87d2..7735f8ce899 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php @@ -17,6 +17,7 @@ use Magento\Store\Model\StoreManagerInterface; /** + * Import offline shipping. * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Import @@ -87,6 +88,8 @@ public function __construct( } /** + * Check if there are errors. + * * @return bool */ public function hasErrors() @@ -95,6 +98,8 @@ public function hasErrors() } /** + * Get errors. + * * @return array */ public function getErrors() @@ -103,6 +108,8 @@ public function getErrors() } /** + * Retrieve columns. + * * @return array */ public function getColumns() @@ -111,6 +118,8 @@ public function getColumns() } /** + * Get data from file. + * * @param ReadInterface $file * @param int $websiteId * @param string $conditionShortName @@ -135,7 +144,7 @@ public function getData(ReadInterface $file, $websiteId, $conditionShortName, $c if (empty($csvLine)) { continue; } - $rowData = $this->rowParser->parse( + $rowsData = $this->rowParser->parse( $csvLine, $rowNumber, $websiteId, @@ -144,20 +153,25 @@ public function getData(ReadInterface $file, $websiteId, $conditionShortName, $c $columnResolver ); - // protect from duplicate - $hash = $this->dataHashGenerator->getHash($rowData); - if (array_key_exists($hash, $this->uniqueHash)) { - throw new RowException( - __( - 'Duplicate Row #%1 (duplicates row #%2)', - $rowNumber, - $this->uniqueHash[$hash] - ) - ); + foreach ($rowsData as $rowData) { + // protect from duplicate + $hash = $this->dataHashGenerator->getHash($rowData); + if (array_key_exists($hash, $this->uniqueHash)) { + throw new RowException( + __( + 'Duplicate Row #%1 (duplicates row #%2)', + $rowNumber, + $this->uniqueHash[$hash] + ) + ); + } + $this->uniqueHash[$hash] = $rowNumber; + + $items[] = $rowData; + } + if (count($rowsData) > 1) { + $bunchSize += count($rowsData) - 1; } - $this->uniqueHash[$hash] = $rowNumber; - - $items[] = $rowData; if (count($items) === $bunchSize) { yield $items; $items = []; @@ -172,6 +186,8 @@ public function getData(ReadInterface $file, $websiteId, $conditionShortName, $c } /** + * Retrieve column headers. + * * @param ReadInterface $file * @return array|bool * @throws LocalizedException diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php index 1a311f3658a..e015f7b5463 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php @@ -6,6 +6,9 @@ namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; +/** + * Location directory. + */ class LocationDirectory { /** @@ -13,6 +16,11 @@ class LocationDirectory */ protected $regions; + /** + * @var array + */ + private $regionsByCode; + /** * @var array */ @@ -47,6 +55,8 @@ public function __construct( } /** + * Retrieve country id. + * * @param string $countryCode * @return null|string */ @@ -88,6 +98,8 @@ protected function loadCountries() } /** + * Check if there is country id with provided country code. + * * @param string $countryCode * @return bool */ @@ -98,6 +110,8 @@ public function hasCountryId($countryCode) } /** + * Check if there is region id with provided region code and country id. + * * @param string $countryId * @param string $regionCode * @return bool @@ -115,29 +129,50 @@ public function hasRegionId($countryId, $regionCode) */ protected function loadRegions() { - if ($this->regions !== null) { + if ($this->regions !== null && $this->regionsByCode !== null) { return $this; } $this->regions = []; + $this->regionsByCode = []; /** @var $collection \Magento\Directory\Model\ResourceModel\Region\Collection */ $collection = $this->_regionCollectionFactory->create(); foreach ($collection->getData() as $row) { $this->regions[$row['country_id']][$row['code']] = (int)$row['region_id']; + if (empty($this->regionsByCode[$row['country_id']][$row['code']])) { + $this->regionsByCode[$row['country_id']][$row['code']] = []; + } + $this->regionsByCode[$row['country_id']][$row['code']][] = (int)$row['region_id']; } return $this; } /** + * Retrieve region id. + * * @param int $countryId * @param string $regionCode * @return string + * @deprecated */ public function getRegionId($countryId, $regionCode) { $this->loadRegions(); return $this->regions[$countryId][$regionCode]; } + + /** + * Return region ids for country and region + * + * @param int $countryId + * @param string $regionCode + * @return array + */ + public function getRegionIds($countryId, $regionCode) + { + $this->loadRegions(); + return $this->regionsByCode[$countryId][$regionCode]; + } } diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php index 5b03ef0cb02..f7105b8e547 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php @@ -6,6 +6,9 @@ namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; +/** + * Query builder for table rate + */ class RateQuery { /** @@ -24,6 +27,8 @@ public function __construct( } /** + * Prepare select + * * @param \Magento\Framework\DB\Select $select * @return \Magento\Framework\DB\Select */ @@ -42,6 +47,7 @@ public function prepareSelect(\Magento\Framework\DB\Select $select) ') OR (', [ "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode", + "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode_prefix", "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = ''", // Handle asterisk in dest_zip field @@ -51,7 +57,7 @@ public function prepareSelect(\Magento\Framework\DB\Select $select) "dest_country_id = '0' AND dest_region_id = 0 AND dest_zip = '*'", "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = ''", "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = :postcode", - "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = '*'" + "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = :postcode_prefix" ] ) . ')'; $select->where($orWhere); @@ -76,6 +82,8 @@ public function prepareSelect(\Magento\Framework\DB\Select $select) } /** + * Returns query bindings + * * @return array */ public function getBindings() @@ -85,6 +93,7 @@ public function getBindings() ':country_id' => $this->request->getDestCountryId(), ':region_id' => (int)$this->request->getDestRegionId(), ':postcode' => $this->request->getDestPostcode(), + ':postcode_prefix' => $this->getDestPostcodePrefix() ]; // Render condition by condition name @@ -106,10 +115,26 @@ public function getBindings() } /** + * Returns rate request + * * @return \Magento\Quote\Model\Quote\Address\RateRequest */ public function getRequest() { return $this->request; } + + /** + * Returns the entire postcode if it contains no dash or the part of it prior to the dash in the other case + * + * @return string + */ + private function getDestPostcodePrefix() + { + if (!preg_match("/^(.+)-(.+)$/", $this->request->getDestPostcode(), $zipParts)) { + return $this->request->getDestPostcode(); + } + + return $zipParts[1]; + } } diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php index 8c34e9a0d65..683790c5312 100644 --- a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php @@ -37,7 +37,7 @@ class RowParserTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->locationDirectoryMock = $this->getMockBuilder(LocationDirectory::class) - ->setMethods(['hasCountryId', 'getCountryId', 'hasRegionId', 'getRegionId']) + ->setMethods(['hasCountryId', 'getCountryId', 'hasRegionId', 'getRegionIds']) ->disableOriginalConstructor() ->getMock(); $this->columnResolverMock = $this->getMockBuilder(ColumnResolver::class) @@ -92,7 +92,7 @@ public function testParse() $conditionShortName, $columnValueMap ); - $this->assertEquals($expectedResult, $result); + $this->assertEquals([$expectedResult], $result); } /** diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php index 4e433c380f7..722683decb4 100644 --- a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php @@ -77,9 +77,6 @@ protected function setUp() ->getMock(); $this->dataHashGeneratorMock = $this->getMockBuilder(DataHashGenerator::class) ->getMock(); - $this->rowParserMock->expects($this->any()) - ->method('parse') - ->willReturnArgument(0); $this->dataHashGeneratorMock->expects($this->any()) ->method('getHash') ->willReturnCallback( @@ -124,6 +121,15 @@ public function testGetData() ['a4', 'b4', 'c4', 'd4', 'e4'], ['a5', 'b5', 'c5', 'd5', 'e5'], ]; + $this->rowParserMock->expects($this->any()) + ->method('parse') + ->willReturn( + [['a1', 'b1', 'c1', 'd1', 'e1']], + [['a2', 'b2', 'c2', 'd2', 'e2']], + [['a3', 'b3', 'c3', 'd3', 'e3']], + [['a4', 'b4', 'c4', 'd4', 'e4']], + [['a5', 'b5', 'c5', 'd5', 'e5']] + ); $file = $this->createFileMock($lines); $expectedResult = [ [ @@ -167,6 +173,13 @@ public function testGetDataWithDuplicatedLine() [], ['a2', 'b2', 'c2', 'd2', 'e2'], ]; + $this->rowParserMock->expects($this->any()) + ->method('parse') + ->willReturn( + [['a1', 'b1', 'c1', 'd1', 'e1']], + [['a1', 'b1', 'c1', 'd1', 'e1']], + [['a2', 'b2', 'c2', 'd2', 'e2']] + ); $file = $this->createFileMock($lines); $expectedResult = [ [ diff --git a/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php b/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php new file mode 100644 index 00000000000..e16584b0b17 --- /dev/null +++ b/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\PageCache\Model\System\Config\Backend; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +/** + * Access List config field. + */ +class AccessList extends Varnish +{ + /** + * @inheritDoc + */ + public function beforeSave() + { + parent::beforeSave(); + + $value = $this->getValue(); + if (!is_string($value) || !preg_match('/^[\w\s\.\-\,\:]+$/', $value)) { + throw new LocalizedException( + new Phrase( + 'Access List value "%1" is not valid. ' + .'Please use only IP addresses and host names.', + [$value] + ) + ); + } + } +} diff --git a/app/code/Magento/PageCache/Test/Unit/Model/System/Config/Backend/AccessListTest.php b/app/code/Magento/PageCache/Test/Unit/Model/System/Config/Backend/AccessListTest.php new file mode 100644 index 00000000000..e84b412beb8 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Unit/Model/System/Config/Backend/AccessListTest.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\PageCache\Test\Unit\Model\System\Config\Backend; + +use Magento\PageCache\Model\System\Config\Backend\AccessList; +use PHPUnit\Framework\TestCase; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\Config\ScopeConfigInterface; + +class AccessListTest extends TestCase +{ + /** + * @var AccessList + */ + private $accessList; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + $configMock = $this->getMockForAbstractClass( + ScopeConfigInterface::class + ); + $configMock->expects($this->any()) + ->method('getValue') + ->with('system/full_page_cache/default') + ->willReturn(['access_list' => 'localhost']); + $this->accessList = $objectManager->getObject( + AccessList::class, + [ + 'config' => $configMock, + 'data' => ['field' => 'access_list'] + ] + ); + } + + /** + * @return array + */ + public function getValidValues(): array + { + return [ + ['localhost', 'localhost'], + [null, 'localhost'], + ['127.0.0.1', '127.0.0.1'], + ['127.0.0.1, localhost, ::2', '127.0.0.1, localhost, ::2'], + ]; + } + + /** + * @param mixed $value + * @param mixed $expectedValue + * @dataProvider getValidValues + */ + public function testBeforeSave($value, $expectedValue) + { + $this->accessList->setValue($value); + $this->accessList->beforeSave(); + $this->assertEquals($expectedValue, $this->accessList->getValue()); + } + + /** + * @return array + */ + public function getInvalidValues(): array + { + return [ + ['\\bull val\\'], + ['{*I am not an IP*}'], + ['{*I am not an IP*}, 127.0.0.1'], + ]; + } + + /** + * @param mixed $value + * @expectedException \Magento\Framework\Exception\LocalizedException + * @dataProvider getInvalidValues + */ + public function testBeforeSaveInvalid($value) + { + $this->accessList->setValue($value); + $this->accessList->beforeSave(); + } +} diff --git a/app/code/Magento/PageCache/etc/adminhtml/system.xml b/app/code/Magento/PageCache/etc/adminhtml/system.xml index 5055b179568..2a4439ac6a9 100644 --- a/app/code/Magento/PageCache/etc/adminhtml/system.xml +++ b/app/code/Magento/PageCache/etc/adminhtml/system.xml @@ -20,7 +20,7 @@ <label>Access list</label> <comment>IPs access list separated with ',' that can purge Varnish configuration for config file generation. If field is empty default value localhost will be saved.</comment> - <backend_model>Magento\PageCache\Model\System\Config\Backend\Varnish</backend_model> + <backend_model>Magento\PageCache\Model\System\Config\Backend\AccessList</backend_model> <depends> <field id="caching_application">1</field> </depends> diff --git a/app/code/Magento/Payment/etc/config.xml b/app/code/Magento/Payment/etc/config.xml index 9fe859c96a9..663734fb066 100644 --- a/app/code/Magento/Payment/etc/config.xml +++ b/app/code/Magento/Payment/etc/config.xml @@ -13,6 +13,7 @@ <model>Magento\Payment\Model\Method\Free</model> <order_status>pending</order_status> <title>No Payment Information Required + authorize 0 1 diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Billing/Agreement/Index.php b/app/code/Magento/Paypal/Controller/Adminhtml/Billing/Agreement/Index.php index b4c68df6e29..ca92b6a044f 100644 --- a/app/code/Magento/Paypal/Controller/Adminhtml/Billing/Agreement/Index.php +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Billing/Agreement/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Paypal\Controller\Adminhtml\Billing\Agreement; -class Index extends \Magento\Paypal\Controller\Adminhtml\Billing\Agreement +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Paypal\Controller\Adminhtml\Billing\Agreement implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Paypal/Reports/Index.php b/app/code/Magento/Paypal/Controller/Adminhtml/Paypal/Reports/Index.php index 91f3e8d9065..c2cee0ffbc6 100644 --- a/app/code/Magento/Paypal/Controller/Adminhtml/Paypal/Reports/Index.php +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Paypal/Reports/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Paypal\Controller\Adminhtml\Paypal\Reports; -class Index extends \Magento\Paypal\Controller\Adminhtml\Paypal\Reports +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Paypal\Controller\Adminhtml\Paypal\Reports implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Paypal/Controller/Express/GetToken.php b/app/code/Magento/Paypal/Controller/Express/GetToken.php index 7c127803cb0..93e9abb4c09 100644 --- a/app/code/Magento/Paypal/Controller/Express/GetToken.php +++ b/app/code/Magento/Paypal/Controller/Express/GetToken.php @@ -5,6 +5,7 @@ */ namespace Magento\Paypal\Controller\Express; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Checkout\Helper\Data; use Magento\Checkout\Helper\ExpressRedirect; use Magento\Checkout\Model\Type\Onepage; @@ -19,7 +20,7 @@ * Class GetToken * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class GetToken extends AbstractExpress +class GetToken extends AbstractExpress implements HttpGetActionInterface { /** * Config mode type diff --git a/app/code/Magento/Paypal/Controller/Express/Start.php b/app/code/Magento/Paypal/Controller/Express/Start.php index e0a3c5381be..b381c413071 100644 --- a/app/code/Magento/Paypal/Controller/Express/Start.php +++ b/app/code/Magento/Paypal/Controller/Express/Start.php @@ -6,7 +6,9 @@ */ namespace Magento\Paypal\Controller\Express; -class Start extends \Magento\Paypal\Controller\Express\AbstractExpress\Start +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Start extends \Magento\Paypal\Controller\Express\AbstractExpress\Start implements HttpGetActionInterface { /** * Config mode type diff --git a/app/code/Magento/Paypal/Controller/Payflowexpress/Start.php b/app/code/Magento/Paypal/Controller/Payflowexpress/Start.php index a6fbcfd0239..4615320c945 100644 --- a/app/code/Magento/Paypal/Controller/Payflowexpress/Start.php +++ b/app/code/Magento/Paypal/Controller/Payflowexpress/Start.php @@ -6,7 +6,9 @@ */ namespace Magento\Paypal\Controller\Payflowexpress; -class Start extends \Magento\Paypal\Controller\Express\AbstractExpress\Start +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Start extends \Magento\Paypal\Controller\Express\AbstractExpress\Start implements HttpGetActionInterface { /** * Config mode type diff --git a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php index 2efae34a964..85907c9d371 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php +++ b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php @@ -5,6 +5,7 @@ */ namespace Magento\Paypal\Controller\Transparent; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\Result\JsonFactory; @@ -21,7 +22,7 @@ * @package Magento\Paypal\Controller\Transparent * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class RequestSecureToken extends \Magento\Framework\App\Action\Action +class RequestSecureToken extends \Magento\Framework\App\Action\Action implements HttpPostActionInterface { /** * @var JsonFactory diff --git a/app/code/Magento/Paypal/Model/AbstractConfig.php b/app/code/Magento/Paypal/Model/AbstractConfig.php index 3b0f7b97482..e5beddac3b1 100644 --- a/app/code/Magento/Paypal/Model/AbstractConfig.php +++ b/app/code/Magento/Paypal/Model/AbstractConfig.php @@ -134,7 +134,7 @@ public function setStoreId($storeId) * Returns payment configuration value * * @param string $key - * @param null $storeId + * @param null|int $storeId * @return null|string * * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -224,15 +224,26 @@ protected function _prepareValue($key, $value) */ public function shouldUseUnilateralPayments() { - return $this->getValue('business_account') && !$this->isWppApiAvailabe(); + return $this->getValue('business_account') && !$this->isWppApiAvailable(); } /** * Check whether WPP API credentials are available for this method * + * @deprecated * @return bool */ public function isWppApiAvailabe() + { + return $this->isWppApiAvailable(); + } + + /** + * Check whether WPP API credentials are available for this method + * + * @return bool + */ + public function isWppApiAvailable() { return $this->getValue('api_username') && $this->getValue('api_password') @@ -243,7 +254,7 @@ public function isWppApiAvailabe() /** * Check whether method available for checkout or not * - * @param null $methodCode + * @param null|string $methodCode * * @return bool */ diff --git a/app/code/Magento/Paypal/Model/Api/PayflowNvp.php b/app/code/Magento/Paypal/Model/Api/PayflowNvp.php index 6373c6d99fb..b769cbe4364 100644 --- a/app/code/Magento/Paypal/Model/Api/PayflowNvp.php +++ b/app/code/Magento/Paypal/Model/Api/PayflowNvp.php @@ -136,6 +136,9 @@ class PayflowNvp extends \Magento\Paypal\Model\Api\Nvp 'CVV2MATCH' => 'cvv2_check_result', 'USERSELECTEDFUNDINGSOURCE' => 'funding_source', + + 'NOSHIPPING' => 'suppress_shipping', + 'REQBILLINGADDRESS' => 'require_billing_address', ]; /** @@ -248,6 +251,8 @@ class PayflowNvp extends \Magento\Paypal\Model\Api\Nvp 'PAYFLOWCOLOR', 'LOCALECODE', 'USERSELECTEDFUNDINGSOURCE', + 'NOSHIPPING', + 'REQBILLINGADDRESS', ]; /** @@ -727,6 +732,7 @@ protected function _prepareExpressCheckoutCallRequest(&$requestFields) /** * Additional response processing. + * * Hack to cut off length from API type response params. * * @param array $response @@ -784,7 +790,8 @@ protected function _exportLineItems(array &$request, $i = 0) } /** - * Set specific data when negative line item case + * Set specific data when negative line item case. + * * @return void */ protected function _setSpecificForNegativeLineItems() diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php index 34e40ac7509..b058ba129a3 100644 --- a/app/code/Magento/Paypal/Model/Config.php +++ b/app/code/Magento/Paypal/Model/Config.php @@ -10,6 +10,7 @@ /** * Config model that is aware of all \Magento\Paypal payment methods + * * Works with PayPal-specific system configuration * @SuppressWarnings(PHPMD.ExcessivePublicCount) @@ -632,6 +633,7 @@ public function __construct( /** * Check whether method available for checkout or not + * * Logic based on merchant country, methods dependence * * @param string|null $methodCode @@ -677,7 +679,7 @@ public function isMethodAvailable($methodCode = null) } break; case self::METHOD_BILLING_AGREEMENT: - $result = $this->isWppApiAvailabe(); + $result = $this->isWppApiAvailable(); break; } return $result; @@ -723,6 +725,7 @@ public function getMerchantCountry() /** * Check whether method supported for specified country or not + * * Use $_methodCode and merchant country by default * * @param string|null $method @@ -896,6 +899,7 @@ public function getExpressCheckoutEditUrl($token) /** * Get url for additional actions that PayPal may require customer to do after placing the order. + * * For instance, redirecting customer to bank for payment confirmation. * * @param string $token @@ -957,6 +961,7 @@ public function areButtonsDynamic() /** * Express checkout shortcut pic URL getter + * * PayPal will ignore "pal", if there is no total amount specified * * @param string $localeCode @@ -996,6 +1001,7 @@ public function getExpressCheckoutInContextImageUrl($localeCode) /** * Get PayPal "mark" image URL + * * Supposed to be used on payment methods selection * $staticSize is applicable for static images only * @@ -1032,6 +1038,7 @@ public function getPaymentMarkImageUrl($localeCode, $orderTotal = null, $pal = n /** * Get "What Is PayPal" localized URL + * * Supposed to be used with "mark" as popup window * * @param \Magento\Framework\Locale\ResolverInterface $localeResolver @@ -1262,6 +1269,7 @@ public function getExpressCheckoutBASignupOptions() /** * Whether to ask customer to create billing agreements + * * Unilateral payments are incompatible with the billing agreements * * @return bool @@ -1376,6 +1384,7 @@ public function exportExpressCheckoutStyleSettings(\Magento\Framework\DataObject /** * Dynamic PayPal image URL getter + * * Also can render dynamic Acceptance Mark * * @param string $type @@ -1725,6 +1734,7 @@ public function getBmlPublisherId() /** * Get Display option from stored config + * * @param string $section * * @return mixed @@ -1752,6 +1762,7 @@ public function getBmlDisplay($section) /** * Get Position option from stored config + * * @param string $section * * @return mixed @@ -1767,6 +1778,7 @@ public function getBmlPosition($section) /** * Get Size option from stored config + * * @param string $section * * @return mixed diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index 1300c793689..856e01f7353 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -1056,10 +1056,7 @@ protected function _prepareShippingOptions(Address $address, $mayReturnEmpty = f */ protected static function cmpShippingOptions(DataObject $option1, DataObject $option2) { - if ($option1->getAmount() == $option2->getAmount()) { - return 0; - } - return ($option1->getAmount() < $option2->getAmount()) ? -1 : 1; + return $option1->getAmount() <=> $option2->getAmount(); } /** diff --git a/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php index 9ec93182126..78bd269403b 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php @@ -189,14 +189,14 @@ public function getValueDataProvider() * * @dataProvider isWppApiAvailabeDataProvider */ - public function testIsWppApiAvailabe($returnMap, $expectedValue) + public function testIsWppApiAvailable($returnMap, $expectedValue) { $this->config->setMethod('paypal_express'); $this->scopeConfigMock->expects($this->any()) ->method('getValue') ->willReturnMap($returnMap); - $this->assertEquals($expectedValue, $this->config->isWppApiAvailabe()); + $this->assertEquals($expectedValue, $this->config->isWppApiAvailable()); } /** diff --git a/app/code/Magento/Paypal/etc/db_schema.xml b/app/code/Magento/Paypal/etc/db_schema.xml index 2703ee4f5be..0441231e64a 100644 --- a/app/code/Magento/Paypal/etc/db_schema.xml +++ b/app/code/Magento/Paypal/etc/db_schema.xml @@ -134,7 +134,7 @@
+ comment="Entity ID"/> diff --git a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php index 23721cb4b16..914b5fa2717 100644 --- a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php +++ b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php @@ -6,6 +6,7 @@ namespace Magento\ProductVideo\Controller\Adminhtml\Product\Gallery; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\File\Uploader; @@ -13,7 +14,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class RetrieveImage extends \Magento\Backend\App\Action +class RetrieveImage extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/ProductVideo/i18n/de_DE.csv b/app/code/Magento/ProductVideo/i18n/de_DE.csv index 70473173969..ca24668bb8d 100644 --- a/app/code/Magento/ProductVideo/i18n/de_DE.csv +++ b/app/code/Magento/ProductVideo/i18n/de_DE.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/en_US.csv b/app/code/Magento/ProductVideo/i18n/en_US.csv index 2d226c6daef..debcab151cc 100644 --- a/app/code/Magento/ProductVideo/i18n/en_US.csv +++ b/app/code/Magento/ProductVideo/i18n/en_US.csv @@ -40,3 +40,4 @@ Delete,Delete "Autostart base video","Autostart base video" "Show related video","Show related video" "Auto restart video","Auto restart video" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/es_ES.csv b/app/code/Magento/ProductVideo/i18n/es_ES.csv index 70473173969..ca24668bb8d 100644 --- a/app/code/Magento/ProductVideo/i18n/es_ES.csv +++ b/app/code/Magento/ProductVideo/i18n/es_ES.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/fr_FR.csv b/app/code/Magento/ProductVideo/i18n/fr_FR.csv index 70473173969..ca24668bb8d 100644 --- a/app/code/Magento/ProductVideo/i18n/fr_FR.csv +++ b/app/code/Magento/ProductVideo/i18n/fr_FR.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/nl_NL.csv b/app/code/Magento/ProductVideo/i18n/nl_NL.csv index 70473173969..5ad83865730 100644 --- a/app/code/Magento/ProductVideo/i18n/nl_NL.csv +++ b/app/code/Magento/ProductVideo/i18n/nl_NL.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/pt_BR.csv b/app/code/Magento/ProductVideo/i18n/pt_BR.csv index 70473173969..5ad83865730 100644 --- a/app/code/Magento/ProductVideo/i18n/pt_BR.csv +++ b/app/code/Magento/ProductVideo/i18n/pt_BR.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv index 70473173969..5ad83865730 100644 --- a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv +++ b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" \ No newline at end of file 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 index f5a22c50e6d..63bd5321ad3 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml +++ b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml @@ -6,6 +6,9 @@ */ --> + + + diff --git a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml index 8b0f7d8ed98..6dff5321189 100755 --- a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml +++ b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml @@ -140,30 +140,37 @@ $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'to alt="<%- data.label %>"/>
- + <%} %> + + +
escapeHtml( @@ -329,4 +336,4 @@ $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'to
+ \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css b/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css new file mode 100644 index 00000000000..ad779c3f29b --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +.gallery .tooltip .delete-tooltiptext { + visibility: hidden; + width: 112px; + background-color: #373330; + color: #F7F3EB; + text-align: center; + padding: 5px 0; + position: absolute; + z-index: 1; + left: 30px; + top: 91px; +} + +.gallery .tooltip:hover .delete-tooltiptext { + visibility: visible; +} \ No newline at end of file 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 index 287c88e8a79..e9b234c5f11 100644 --- 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 @@ -87,6 +87,9 @@ define([ * @private */ _doUpdate: function () { + var uploaderLinkUrl, + uploaderLink; + this.reset(); this.element.find(this.options.container).append( '
' + - this.options.metaData.data.uploader + - '' - ); + uploaderLinkUrl = 'https://youtube.com/channel/' + this.options.metaData.data.uploaderUrl; } else if (this.options.videoProvider === 'vimeo') { - this.element.find(this.options.metaData.DOM.uploader).html( - '' + this.options.metaData.data.uploader + - ''); + uploaderLinkUrl = this.options.metaData.data.uploaderUrl; } + uploaderLink = document.createElement('a'); + uploaderLink.setAttribute('href', uploaderLinkUrl); + uploaderLink.setAttribute('target', '_blank'); + uploaderLink.innerText = this.options.metaData.data.uploader; + this.element.find(this.options.metaData.DOM.uploader)[0].appendChild(uploaderLink); this.element.find('.' + this.options.videoClass).productVideoLoader(); }, diff --git a/app/code/Magento/Quote/Model/MaskedQuoteIdToQuoteId.php b/app/code/Magento/Quote/Model/MaskedQuoteIdToQuoteId.php index f30d98342be..37a8fcd494f 100644 --- a/app/code/Magento/Quote/Model/MaskedQuoteIdToQuoteId.php +++ b/app/code/Magento/Quote/Model/MaskedQuoteIdToQuoteId.php @@ -10,6 +10,9 @@ use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResource; +/** + * MaskedQuoteId to QuoteId resolver + */ class MaskedQuoteIdToQuoteId implements MaskedQuoteIdToQuoteIdInterface { /** diff --git a/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php b/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php index 5ddadfc22f5..2e802f47cfe 100644 --- a/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php +++ b/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php @@ -8,29 +8,40 @@ namespace Magento\Quote\Model; use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResource; +/** + * QuoteId to MaskedQuoteId resolver + */ class QuoteIdToMaskedQuoteId implements QuoteIdToMaskedQuoteIdInterface { /** * @var QuoteIdMaskFactory */ private $quoteIdMaskFactory; - /** * @var CartRepositoryInterface */ private $cartRepository; + /** + * @var QuoteIdMaskResource + */ + private $quoteIdMaskResource; + /** * @param QuoteIdMaskFactory $quoteIdMaskFactory * @param CartRepositoryInterface $cartRepository + * @param QuoteIdMaskResource $quoteIdMaskResource */ public function __construct( QuoteIdMaskFactory $quoteIdMaskFactory, - CartRepositoryInterface $cartRepository + CartRepositoryInterface $cartRepository, + QuoteIdMaskResource $quoteIdMaskResource ) { $this->quoteIdMaskFactory = $quoteIdMaskFactory; $this->cartRepository = $cartRepository; + $this->quoteIdMaskResource = $quoteIdMaskResource; } /** @@ -42,8 +53,9 @@ public function execute(int $quoteId): string $this->cartRepository->get($quoteId); $quoteIdMask = $this->quoteIdMaskFactory->create(); - $quoteIdMask->setQuoteId($quoteId)->save(); + $this->quoteIdMaskResource->load($quoteIdMask, $quoteId, 'quote_id'); + $maskedId = $quoteIdMask->getMaskedId() ?? ''; - return $quoteIdMask->getMaskedId(); + return $maskedId; } } diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 3bd7122e65d..725825e976a 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
+ comment="Entity ID"/>
+ comment="Entity ID"/> diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php new file mode 100644 index 00000000000..96259f22649 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -0,0 +1,81 @@ +cartRepository = $cartRepository; + $this->addProductToCart = $addProductToCart; + } + + /** + * Add products to cart + * + * @param Quote $cart + * @param array $cartItems + * @throws GraphQlInputException + */ + public function execute(Quote $cart, array $cartItems): void + { + foreach ($cartItems as $cartItemData) { + $this->addProductToCart->execute($cart, $cartItemData); + } + + if ($cart->getData('has_error')) { + throw new GraphQlInputException( + __('Shopping cart error: %message', ['message' => $this->getCartErrors($cart)]) + ); + } + + $this->cartRepository->save($cart); + } + + /** + * Collecting cart errors + * + * @param Quote $cart + * @return string + */ + private function getCartErrors(Quote $cart): string + { + $errorMessages = []; + + /** @var AbstractMessage $error */ + foreach ($cart->getErrors() as $error) { + $errorMessages[] = $error->getText(); + } + + return implode(PHP_EOL, $errorMessages); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php new file mode 100644 index 00000000000..aa5b41daebd --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -0,0 +1,149 @@ +arrayManager = $arrayManager; + $this->dataObjectFactory = $dataObjectFactory; + $this->productRepository = $productRepository; + } + + /** + * Add simple product to cart + * + * @param Quote $cart + * @param array $cartItemData + * @return void + * @throws GraphQlNoSuchEntityException + * @throws GraphQlInputException + */ + public function execute(Quote $cart, array $cartItemData): void + { + $sku = $this->extractSku($cartItemData); + $qty = $this->extractQty($cartItemData); + $customizableOptions = $this->extractCustomizableOptions($cartItemData); + + try { + $product = $this->productRepository->get($sku); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); + } + + $result = $cart->addProduct($product, $this->createBuyRequest($qty, $customizableOptions)); + + if (is_string($result)) { + throw new GraphQlInputException(__($result)); + } + } + + /** + * Extract SKU from cart item data + * + * @param array $cartItemData + * @return string + * @throws GraphQlInputException + */ + private function extractSku(array $cartItemData): string + { + $sku = $this->arrayManager->get('data/sku', $cartItemData); + if (!isset($sku)) { + throw new GraphQlInputException(__('Missing key "sku" in cart item data')); + } + return (string)$sku; + } + + /** + * Extract Qty from cart item data + * + * @param array $cartItemData + * @return float + * @throws GraphQlInputException + */ + private function extractQty(array $cartItemData): float + { + $qty = $this->arrayManager->get('data/qty', $cartItemData); + if (!isset($qty)) { + throw new GraphQlInputException(__('Missing key "qty" in cart item data')); + } + return (float)$qty; + } + + /** + * Extract Customizable Options from cart item data + * + * @param array $cartItemData + * @return array + */ + private function extractCustomizableOptions(array $cartItemData): array + { + $customizableOptions = $this->arrayManager->get('customizable_options', $cartItemData, []); + + $customizableOptionsData = []; + foreach ($customizableOptions as $customizableOption) { + $customizableOptionsData[$customizableOption['id']] = $customizableOption['value']; + } + return $customizableOptionsData; + } + + /** + * Format GraphQl input data to a shape that buy request has + * + * @param float $qty + * @param array $customOptions + * @return DataObject + */ + private function createBuyRequest(float $qty, array $customOptions): DataObject + { + return $this->dataObjectFactory->create([ + 'data' => [ + 'qty' => $qty, + 'options' => $customOptions, + ], + ]); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php new file mode 100644 index 00000000000..faefa686606 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php @@ -0,0 +1,47 @@ +getAllItems() as $cartItem) { + $productData = $cartItem->getProduct()->getData(); + $productData['model'] = $cartItem->getProduct(); + + $items[] = [ + 'id' => $cartItem->getItemId(), + 'qty' => $cartItem->getQty(), + 'product' => $productData, + 'model' => $cartItem, + ]; + } + + return [ + 'items' => $items, + ]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php new file mode 100644 index 00000000000..9c50d4b8557 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -0,0 +1,89 @@ +maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; + $this->cartRepository = $cartRepository; + } + + /** + * Get cart for user + * + * @param string $cartHash + * @param int|null $userId + * @return Quote + * @throws GraphQlAuthenticationException + * @throws GraphQlNoSuchEntityException + */ + public function execute(string $cartHash, ?int $userId): Quote + { + try { + $cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash); + } catch (NoSuchEntityException $exception) { + throw new GraphQlNoSuchEntityException( + __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) + ); + } + + try { + /** @var Quote $cart */ + $cart = $this->cartRepository->get($cartId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException( + __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) + ); + } + + $customerId = (int)$cart->getCustomerId(); + + /* Guest cart, allow operations */ + if (!$customerId) { + return $cart; + } + + if ($customerId !== $userId) { + throw new GraphQlAuthenticationException( + __( + 'The current user cannot perform operations on cart "%masked_cart_id"', + ['masked_cart_id' => $cartHash] + ) + ); + } + return $cart; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOption.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOption.php new file mode 100644 index 00000000000..3199668060e --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOption.php @@ -0,0 +1,66 @@ +customizableOptionValue = $customOptionValueDataProvider; + } + + /** + * Retrieve custom option data + * + * @param QuoteItem $cartItem + * @param int $optionId + * @return array + * @throws LocalizedException + */ + public function getData(QuoteItem $cartItem, int $optionId): array + { + $product = $cartItem->getProduct(); + $option = $product->getOptionById($optionId); + + if (!$option) { + return []; + } + + $selectedOption = $cartItem->getOptionByCode('option_' . $option->getId()); + + $selectedOptionValueData = $this->customizableOptionValue->getData( + $cartItem, + $option, + $selectedOption + ); + + return [ + 'id' => $option->getId(), + 'label' => $option->getTitle(), + 'type' => $option->getType(), + 'values' => $selectedOptionValueData, + 'sort_order' => $option->getSortOrder(), + 'is_required' => $option->getIsRequire(), + ]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Composite.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Composite.php new file mode 100644 index 00000000000..52978451253 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Composite.php @@ -0,0 +1,68 @@ +objectManager = $objectManager; + $this->customizableOptionValues = $customizableOptionValues; + } + + /** + * @inheritdoc + */ + public function getData( + QuoteItem $cartItem, + Option $option, + SelectedOption $selectedOption + ): array { + $optionType = $option->getType(); + + if (!array_key_exists($optionType, $this->customizableOptionValues)) { + throw new GraphQlInputException(__('Option type "%1" is not supported', $optionType)); + } + $customizableOptionValueClassName = $this->customizableOptionValues[$optionType]; + + $customizableOptionValue = $this->objectManager->get($customizableOptionValueClassName); + if (!$customizableOptionValue instanceof CustomizableOptionValueInterface) { + throw new LocalizedException( + __('%1 doesn\'t implement CustomizableOptionValueInterface', $customizableOptionValueClassName) + ); + } + return $customizableOptionValue->getData($cartItem, $option, $selectedOption); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php new file mode 100644 index 00000000000..74ed4034650 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php @@ -0,0 +1,65 @@ +priceUnitLabel = $priceUnitLabel; + } + + /** + * @inheritdoc + */ + public function getData( + QuoteItem $cartItem, + Option $option, + SelectedOption $selectedOption + ): array { + /** @var SelectOptionType $optionTypeRenderer */ + $optionTypeRenderer = $option->groupFactory($option->getType()) + ->setOption($option) + ->setConfigurationItemOption($selectedOption); + + $selectedValue = $selectedOption->getValue(); + $optionValue = $option->getValueById($selectedValue); + $optionPriceType = (string)$optionValue->getPriceType(); + $priceValueUnits = $this->priceUnitLabel->getData($optionPriceType); + + $selectedOptionValueData = [ + 'id' => $selectedOption->getId(), + 'label' => $optionTypeRenderer->getFormattedOptionValue($selectedValue), + 'value' => $selectedValue, + 'price' => [ + 'type' => strtoupper($optionPriceType), + 'units' => $priceValueUnits, + 'value' => $optionValue->getPrice(), + ] + ]; + return [$selectedOptionValueData]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php new file mode 100644 index 00000000000..619e84568a5 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php @@ -0,0 +1,67 @@ +priceUnitLabel = $priceUnitLabel; + } + + /** + * @inheritdoc + */ + public function getData( + QuoteItem $cartItem, + Option $option, + SelectedOption $selectedOption + ): array { + $selectedOptionValueData = []; + $optionIds = explode(',', $selectedOption->getValue()); + + if (0 === count($optionIds)) { + return $selectedOptionValueData; + } + + foreach ($optionIds as $optionId) { + $optionValue = $option->getValueById($optionId); + $priceValueUnits = $this->priceUnitLabel->getData($optionValue->getPriceType()); + + $selectedOptionValueData[] = [ + 'id' => $selectedOption->getId(), + 'label' => $optionValue->getTitle(), + 'price' => [ + 'type' => strtoupper($optionValue->getPriceType()), + 'units' => $priceValueUnits, + 'value' => $optionValue->getPrice(), + ], + ]; + } + + return $selectedOptionValueData; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/PriceUnitLabel.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/PriceUnitLabel.php new file mode 100644 index 00000000000..bee2e54ed5f --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/PriceUnitLabel.php @@ -0,0 +1,63 @@ +storeManager = $storeManager; + } + + /** + * Retrieve price value unit + * + * @param string $priceType + * @return string + */ + public function getData(string $priceType): string + { + if (ProductPriceOptionsInterface::VALUE_PERCENT == $priceType) { + return '%'; + } + + return $this->getCurrencySymbol(); + } + + /** + * Get currency symbol + * + * @return string + * @throws NoSuchEntityException + */ + private function getCurrencySymbol(): string + { + /** @var Store|StoreInterface $store */ + $store = $this->storeManager->getStore(); + + return $store->getBaseCurrency()->getCurrencySymbol(); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php new file mode 100644 index 00000000000..4b29eb6a4a6 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Text.php @@ -0,0 +1,59 @@ +priceUnitLabel = $priceUnitLabel; + } + + /** + * @inheritdoc + */ + public function getData( + QuoteItem $cartItem, + Option $option, + SelectedOption $selectedOption + ): array { + /** @var TextOptionType $optionTypeRenderer */ + $optionTypeRenderer = $option->groupFactory($option->getType()); + $priceValueUnits = $this->priceUnitLabel->getData($option->getPriceType()); + + $selectedOptionValueData = [ + 'id' => $selectedOption->getId(), + 'label' => '', + 'value' => $optionTypeRenderer->getFormattedOptionValue($selectedOption->getValue()), + 'price' => [ + 'type' => strtoupper($option->getPriceType()), + 'units' => $priceValueUnits, + 'value' => $option->getPrice(), + ], + ]; + return [$selectedOptionValueData]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValueInterface.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValueInterface.php new file mode 100644 index 00000000000..fce8b80f6d4 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValueInterface.php @@ -0,0 +1,32 @@ +arrayManager = $arrayManager; + $this->getCartForUser = $getCartForUser; + $this->addProductsToCart = $addProductsToCart; + $this->extractDataFromCart = $extractDataFromCart; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + $cartHash = $this->arrayManager->get('input/cart_id', $args); + $cartItems = $this->arrayManager->get('input/cartItems', $args); + + if (!isset($cartHash)) { + throw new GraphQlInputException(__('Missing key "cart_id" in cart data')); + } + + if (!isset($cartItems) || !is_array($cartItems) || empty($cartItems)) { + throw new GraphQlInputException(__('Missing key "cartItems" in cart data')); + } + + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute((string)$cartHash, $currentUserId); + + $this->addProductsToCart->execute($cart, $cartItems); + $cartData = $this->extractDataFromCart->execute($cart); + + return [ + 'cart' => $cartData, + ]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php new file mode 100644 index 00000000000..ec59416d493 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php @@ -0,0 +1,88 @@ +getCartForUser = $getCartForUser; + $this->couponManagement = $couponManagement; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['input']['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['input']['cart_id']; + + if (!isset($args['input']['coupon_code'])) { + throw new GraphQlInputException(__('Required parameter "coupon_code" is missing')); + } + $couponCode = $args['input']['coupon_code']; + + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId); + $cartId = $cart->getId(); + + /* Check current cart does not have coupon code applied */ + $appliedCouponCode = $this->couponManagement->get($cartId); + if (!empty($appliedCouponCode)) { + throw new GraphQlInputException( + __('A coupon is already applied to the cart. Please remove it to apply another') + ); + } + + try { + $this->couponManagement->set($cartId, $couponCode); + } catch (NoSuchEntityException $exception) { + throw new GraphQlNoSuchEntityException(__($exception->getMessage())); + } catch (CouldNotSaveException $exception) { + throw new LocalizedException(__($exception->getMessage())); + } + + $data['cart']['applied_coupon'] = [ + 'code' => $couponCode, + ]; + return $data; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItemTypeResolver.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItemTypeResolver.php new file mode 100644 index 00000000000..962463860a5 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItemTypeResolver.php @@ -0,0 +1,54 @@ +supportedTypes = $supportedTypes; + } + + /** + * @inheritdoc + */ + public function resolveType(array $data) : string + { + if (!isset($data['product'])) { + throw new LocalizedException(__('Missing key "product" in cart data')); + } + $productData = $data['product']; + + if (!isset($productData['type_id'])) { + throw new LocalizedException(__('Missing key "type_id" in product data')); + } + $productTypeId = $productData['type_id']; + + if (!isset($this->supportedTypes[$productTypeId])) { + throw new LocalizedException( + __('Product "%product_type" type is not supported', ['product_type' => $productTypeId]) + ); + } + return $this->supportedTypes[$productTypeId]; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart/CreateEmptyCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php similarity index 70% rename from app/code/Magento/QuoteGraphQl/Model/Resolver/Cart/CreateEmptyCart.php rename to app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php index fcf23dd9f68..06123abe615 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart/CreateEmptyCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php @@ -5,15 +5,15 @@ */ declare(strict_types=1); -namespace Magento\QuoteGraphQl\Model\Resolver\Cart; +namespace Magento\QuoteGraphQl\Model\Resolver; -use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Api\CartManagementInterface; use Magento\Quote\Api\GuestCartManagementInterface; use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\QuoteIdMaskFactory; /** * @inheritdoc @@ -36,26 +36,26 @@ class CreateEmptyCart implements ResolverInterface private $quoteIdToMaskedId; /** - * @var UserContextInterface + * @var QuoteIdMaskFactory */ - private $userContext; + private $quoteIdMaskFactory; /** * @param CartManagementInterface $cartManagement * @param GuestCartManagementInterface $guestCartManagement - * @param UserContextInterface $userContext * @param QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId + * @param QuoteIdMaskFactory $quoteIdMaskFactory */ public function __construct( CartManagementInterface $cartManagement, GuestCartManagementInterface $guestCartManagement, - UserContextInterface $userContext, - QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId + QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId, + QuoteIdMaskFactory $quoteIdMaskFactory ) { $this->cartManagement = $cartManagement; $this->guestCartManagement = $guestCartManagement; - $this->userContext = $userContext; $this->quoteIdToMaskedId = $quoteIdToMaskedId; + $this->quoteIdMaskFactory = $quoteIdMaskFactory; } /** @@ -63,11 +63,17 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - $customerId = $this->userContext->getUserId(); + $customerId = $context->getUserId(); if (0 !== $customerId && null !== $customerId) { $quoteId = $this->cartManagement->createEmptyCartForCustomer($customerId); - $maskedQuoteId = $this->quoteIdToMaskedId->execute($quoteId); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quoteId); + + if (empty($maskedQuoteId)) { + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->setQuoteId($quoteId)->save(); + $maskedQuoteId = $quoteIdMask->getMaskedId(); + } } else { $maskedQuoteId = $this->guestCartManagement->createEmptyCart(); } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomizableOptions.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomizableOptions.php new file mode 100644 index 00000000000..a681b2ca0d0 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomizableOptions.php @@ -0,0 +1,65 @@ +customizableOption = $customizableOption; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + + /** @var QuoteItem $cartItem */ + $cartItem = $value['model']; + $quoteItemOption = $cartItem->getOptionByCode('option_ids'); + + if (null === $quoteItemOption) { + return []; + } + + $customizableOptionsData = []; + $customizableOptionIds = explode(',', $quoteItemOption->getValue()); + + foreach ($customizableOptionIds as $customizableOptionId) { + $customizableOption = $this->customizableOption->getData( + $cartItem, + (int)$customizableOptionId + ); + $customizableOptionsData[] = $customizableOption; + } + return $customizableOptionsData; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php new file mode 100644 index 00000000000..c21d869ddac --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php @@ -0,0 +1,75 @@ +getCartForUser = $getCartForUser; + $this->couponManagement = $couponManagement; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['input']['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['input']['cart_id']; + + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId); + $cartId = $cart->getId(); + + try { + $this->couponManagement->remove($cartId); + } catch (NoSuchEntityException $exception) { + throw new GraphQlNoSuchEntityException(__($exception->getMessage())); + } catch (CouldNotDeleteException $exception) { + throw new LocalizedException(__($exception->getMessage())); + } + + $data['cart']['applied_coupon'] = [ + 'code' => '', + ]; + return $data; + } +} diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 76ecfac373e..c9900dd5f31 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -5,12 +5,12 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", - "magento/module-authorization": "*", - "magento/module-quote": "*" + "magento/module-quote": "*", + "magento/module-catalog": "*", + "magento/module-store": "*" }, "suggest": { - "magento/module-graph-ql": "*", - "magento/module-catalog-graph-ql": "*" + "magento/module-graph-ql": "*" }, "license": [ "OSL-3.0", diff --git a/app/code/Magento/QuoteGraphQl/etc/di.xml b/app/code/Magento/QuoteGraphQl/etc/di.xml new file mode 100644 index 00000000000..63ad9e193b9 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/etc/di.xml @@ -0,0 +1,32 @@ + + + + + + + + SimpleCartItem + + + + + + + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Text + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Text + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Text + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Text + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Text + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Dropdown + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Dropdown + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Dropdown + Magento\QuoteGraphQl\Model\CartItem\DataProvider\CustomizableOptionValue\Multiple + + + + diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 46d1b97d0ae..f692aa57b21 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -2,5 +2,95 @@ # See COPYING.txt for license details. type Mutation { - createEmptyCart: String @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart\\CreateEmptyCart") @doc(description:"Creates empty shopping cart for guest or logged in user") + createEmptyCart: String @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CreateEmptyCart") @doc(description:"Creates empty shopping cart for guest or logged in user") + applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ApplyCouponToCart") + removeCouponFromCart(input: RemoveCouponFromCartInput): RemoveCouponFromCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\RemoveCouponFromCart") + addSimpleProductsToCart(input: AddSimpleProductsToCartInput): AddSimpleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") +} + +input ApplyCouponToCartInput { + cart_id: String! + coupon_code: String! +} + +type ApplyCouponToCartOutput { + cart: Cart! +} + +type Cart { + items: [CartItemInterface] + applied_coupon: AppliedCoupon +} + +type CartAddress { + applied_coupon: AppliedCoupon +} + +type AppliedCoupon { + code: String! +} + +input RemoveCouponFromCartInput { + cart_id: String! +} + +type RemoveCouponFromCartOutput { + cart: Cart +} + +input AddSimpleProductsToCartInput { + cart_id: String! + cartItems: [SimpleProductCartItemInput!]! +} + +input SimpleProductCartItemInput { + data: CartItemInput! + customizable_options:[CustomizableOptionInput!] +} + +input CustomizableOptionInput { + id: Int! + value: String! +} + +type AddSimpleProductsToCartOutput { + cart: Cart! +} + +type SimpleCartItem implements CartItemInterface @doc(description: "Simple Cart Item") { + customizable_options: [SelectedCustomizableOption] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CustomizableOptions") +} + +input CartItemInput { + sku: String! + qty: Float! +} + +interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemTypeResolver") { + id: String! + qty: Float! + product: ProductInterface! +} + +type SelectedCustomizableOption { + id: Int! + label: String! + type: String! + is_required: Int! + values: [SelectedCustomizableOptionValue!]! + sort_order: Int! +} + +type SelectedCustomizableOptionValue { + id: Int! + label: String! + value: String! + price: CartItemSelectedOptionValuePrice! + sort_order: Int! +} + +type CartItemSelectedOptionValuePrice { + value: Float! + units: String! + type: PriceTypeEnum! } diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php index 79deb27423b..f65a2b964fb 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax/Grid.php @@ -53,7 +53,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @codeCoverageIgnore */ protected function _construct() @@ -64,7 +65,7 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ public function getResourceCollectionName() { @@ -74,7 +75,7 @@ public function getResourceCollectionName() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _prepareColumns() { @@ -123,7 +124,6 @@ protected function _prepareColumns() [ 'header' => __('Orders'), 'index' => 'orders_count', - 'total' => 'sum', 'type' => 'number', 'sortable' => false, 'header_css_class' => 'col-qty', diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php index 683ddcd9b66..2fbff13a5b6 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php @@ -20,6 +20,7 @@ * * @api * @since 100.0.2 + * @SuppressWarnings(PHPMD.AllPurposeAction) */ abstract class AbstractReport extends \Magento\Backend\App\Action { @@ -149,15 +150,19 @@ protected function _showLastExecutionTime($flagCode, $refreshCode) } $refreshStatsLink = $this->getUrl('reports/report_statistics'); - $directRefreshLink = $this->getUrl('reports/report_statistics/refreshRecent', ['code' => $refreshCode]); + $directRefreshLink = $this->getUrl('reports/report_statistics/refreshRecent'); $this->messageManager->addNotice( __( 'Last updated: %1. To refresh last day\'s statistics, ' . - 'click here.', + 'click here.', $updatedAt, $refreshStatsLink, - $directRefreshLink + str_replace( + '"', + '"', + json_encode(['action' => $directRefreshLink, 'data' => ['code' => $refreshCode]]) + ) ) ); return $this; diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Accounts.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Accounts.php index ae1c0401add..f8d0cbe9e69 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Accounts.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Accounts.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Customer; -class Accounts extends \Magento\Reports\Controller\Adminhtml\Report\Customer +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Accounts extends \Magento\Reports\Controller\Adminhtml\Report\Customer implements HttpGetActionInterface { /** * New accounts action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Orders.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Orders.php index 56a594ac24e..be46fb6a94c 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Orders.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Orders.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Customer; -class Orders extends \Magento\Reports\Controller\Adminhtml\Report\Customer +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Orders extends \Magento\Reports\Controller\Adminhtml\Report\Customer implements HttpGetActionInterface { /** * Customers by number of orders action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Totals.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Totals.php index 17928872eba..02f40e5be98 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Totals.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Customer/Totals.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Customer; -class Totals extends \Magento\Reports\Controller\Adminhtml\Report\Customer +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Totals extends \Magento\Reports\Controller\Adminhtml\Report\Customer implements HttpGetActionInterface { /** * Customers by orders total action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Downloads.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Downloads.php index e8df3118caa..f2c03d0dc22 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Downloads.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Downloads.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Product; -class Downloads extends \Magento\Reports\Controller\Adminhtml\Report\Product +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Downloads extends \Magento\Reports\Controller\Adminhtml\Report\Product implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Lowstock.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Lowstock.php index b5e3602383f..266d6a85341 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Lowstock.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Lowstock.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Product; -class Lowstock extends \Magento\Reports\Controller\Adminhtml\Report\Product +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Lowstock extends \Magento\Reports\Controller\Adminhtml\Report\Product implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Sold.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Sold.php index 19b8258beaa..f01c46f4c14 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Sold.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Sold.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Product; -class Sold extends \Magento\Reports\Controller\Adminhtml\Report\Product +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Sold extends \Magento\Reports\Controller\Adminhtml\Report\Product implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Viewed.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Viewed.php index 07beec5a727..980540fb1fa 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Viewed.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Product/Viewed.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Viewed extends \Magento\Reports\Controller\Adminhtml\Report\Product +class Viewed extends \Magento\Reports\Controller\Adminhtml\Report\Product implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Customer.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Customer.php index 50b1f79abd4..481522b86f0 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Customer.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Customer.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Review; -class Customer extends \Magento\Reports\Controller\Adminhtml\Report\Review +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Customer extends \Magento\Reports\Controller\Adminhtml\Report\Review implements HttpGetActionInterface { /** * Customer Reviews Report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Product.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Product.php index 3d0ebc5f568..911a313377c 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Product.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Review/Product.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Review; -class Product extends \Magento\Reports\Controller\Adminhtml\Report\Review +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Product extends \Magento\Reports\Controller\Adminhtml\Report\Review implements HttpGetActionInterface { /** * Product reviews report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Bestsellers.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Bestsellers.php index 60f8cc87f2a..eff3796b6d4 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Bestsellers.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Bestsellers.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Sales; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Bestsellers extends \Magento\Reports\Controller\Adminhtml\Report\Sales +class Bestsellers extends \Magento\Reports\Controller\Adminhtml\Report\Sales implements HttpGetActionInterface { /** * Bestsellers report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Coupons.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Coupons.php index 2e324bceee3..d9e83cd77b9 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Coupons.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Coupons.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Sales; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Coupons extends \Magento\Reports\Controller\Adminhtml\Report\Sales +class Coupons extends \Magento\Reports\Controller\Adminhtml\Report\Sales implements HttpGetActionInterface { /** * Coupons report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Invoiced.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Invoiced.php index f533d597990..c26b0f931e6 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Invoiced.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Invoiced.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Sales; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Invoiced extends \Magento\Reports\Controller\Adminhtml\Report\Sales +class Invoiced extends \Magento\Reports\Controller\Adminhtml\Report\Sales implements HttpGetActionInterface { /** * Invoice report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Refunded.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Refunded.php index 85c5fdaed42..f88e9f64b97 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Refunded.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Refunded.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Sales; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Refunded extends \Magento\Reports\Controller\Adminhtml\Report\Sales +class Refunded extends \Magento\Reports\Controller\Adminhtml\Report\Sales implements HttpGetActionInterface { /** * Refunds report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Sales.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Sales.php index d6460864c5c..e3f383ab5a7 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Sales.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Sales.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Sales; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Sales extends \Magento\Reports\Controller\Adminhtml\Report\Sales +class Sales extends \Magento\Reports\Controller\Adminhtml\Report\Sales implements HttpGetActionInterface { /** * Sales report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Tax.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Tax.php index e8ae11088f5..2e6ba487f39 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Tax.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Sales/Tax.php @@ -6,9 +6,10 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Sales; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Model\Flag; -class Tax extends \Magento\Reports\Controller\Adminhtml\Report\Sales +class Tax extends \Magento\Reports\Controller\Adminhtml\Report\Sales implements HttpGetActionInterface { /** * Tax report action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Abandoned.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Abandoned.php index e03f2f65560..3f0567c05a9 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Abandoned.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Abandoned.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Shopcart; -class Abandoned extends \Magento\Reports\Controller\Adminhtml\Report\Shopcart +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Abandoned extends \Magento\Reports\Controller\Adminhtml\Report\Shopcart implements HttpGetActionInterface { /** * Abandoned carts action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Product.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Product.php index 65db66f2d2e..b41c901fe85 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Product.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Shopcart/Product.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Shopcart; -class Product extends \Magento\Reports\Controller\Adminhtml\Report\Shopcart +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Product extends \Magento\Reports\Controller\Adminhtml\Report\Shopcart implements HttpGetActionInterface { /** * Products in carts action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/Index.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/Index.php index d9f9de39657..61ec31337db 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/Index.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Statistics; -class Index extends \Magento\Reports\Controller\Adminhtml\Report\Statistics +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Reports\Controller\Adminhtml\Report\Statistics implements HttpGetActionInterface { /** * Refresh statistics action diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshRecent.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshRecent.php index 1f0f6e8e405..66123938243 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshRecent.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshRecent.php @@ -6,7 +6,12 @@ */ namespace Magento\Reports\Controller\Adminhtml\Report\Statistics; -class RefreshRecent extends \Magento\Reports\Controller\Adminhtml\Report\Statistics +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +/** + * Refresh recent stats. + */ +class RefreshRecent extends \Magento\Reports\Controller\Adminhtml\Report\Statistics implements HttpPostActionInterface { /** * Refresh statistics for last 25 hours diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/Edit.php b/app/code/Magento/Review/Controller/Adminhtml/Product/Edit.php index a39b22ec080..7d922f30cd9 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/Edit.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/Edit.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Framework\Controller\ResultFactory; -class Edit extends ProductController +class Edit extends ProductController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/Index.php b/app/code/Magento/Review/Controller/Adminhtml/Product/Index.php index 0dce5b23883..94c80f89f8d 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/Index.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/Index.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Framework\Controller\ResultFactory; -class Index extends ProductController +class Index extends ProductController implements HttpGetActionInterface { /** * @return \Magento\Framework\Controller\ResultInterface diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php b/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php index c6e9cc81d58..bfd4b5e7470 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php @@ -5,6 +5,7 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Backend\App\Action\Context; use Magento\Framework\Registry; @@ -14,7 +15,7 @@ use Magento\Framework\DataObject; use Magento\Framework\Controller\ResultFactory; -class JsonProductInfo extends ProductController +class JsonProductInfo extends ProductController implements HttpGetActionInterface { /** * @var \Magento\Catalog\Api\ProductRepositoryInterface diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/NewAction.php b/app/code/Magento/Review/Controller/Adminhtml/Product/NewAction.php index 5ac3a6a0572..2709b5ce64b 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/NewAction.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/NewAction.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Framework\Controller\ResultFactory; -class NewAction extends ProductController +class NewAction extends ProductController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/Post.php b/app/code/Magento/Review/Controller/Adminhtml/Product/Post.php index 1f21a52077b..b62fcc7326e 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/Post.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/Post.php @@ -5,12 +5,13 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Framework\Controller\ResultFactory; use Magento\Store\Model\Store; use Magento\Framework\Exception\LocalizedException; -class Post extends ProductController +class Post extends ProductController implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/ProductGrid.php b/app/code/Magento/Review/Controller/Adminhtml/Product/ProductGrid.php index f1b25c3613d..e4057be14af 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/ProductGrid.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/ProductGrid.php @@ -5,6 +5,8 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Backend\App\Action\Context; use Magento\Framework\Registry; @@ -13,7 +15,7 @@ use Magento\Framework\View\LayoutFactory; use Magento\Framework\Controller\ResultFactory; -class ProductGrid extends ProductController +class ProductGrid extends ProductController implements HttpPostActionInterface, HttpGetActionInterface { /** * @var \Magento\Framework\View\LayoutFactory diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/RatingItems.php b/app/code/Magento/Review/Controller/Adminhtml/Product/RatingItems.php index 5b0ab217e71..1da8e4abbd6 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/RatingItems.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/RatingItems.php @@ -5,6 +5,8 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Backend\App\Action\Context; use Magento\Framework\Registry; @@ -13,7 +15,7 @@ use Magento\Framework\View\LayoutFactory; use Magento\Framework\Controller\ResultFactory; -class RatingItems extends ProductController +class RatingItems extends ProductController implements HttpPostActionInterface, HttpGetActionInterface { /** * @var \Magento\Framework\View\LayoutFactory diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php index 7159b1825dc..35187e46933 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php @@ -5,11 +5,12 @@ */ namespace Magento\Review\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Review\Controller\Adminhtml\Product as ProductController; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\LocalizedException; -class Save extends ProductController +class Save extends ProductController implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Review/Controller/Adminhtml/Rating/Delete.php b/app/code/Magento/Review/Controller/Adminhtml/Rating/Delete.php index 5535c3de26e..b25db6e498f 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Rating/Delete.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Rating/Delete.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Rating; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Review\Controller\Adminhtml\Rating as RatingController; use Magento\Framework\Controller\ResultFactory; -class Delete extends RatingController +class Delete extends RatingController implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Review/Controller/Adminhtml/Rating/Edit.php b/app/code/Magento/Review/Controller/Adminhtml/Rating/Edit.php index 1b65966b770..90dac026cfd 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Rating/Edit.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Rating/Edit.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Rating; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Rating as RatingController; use Magento\Framework\Controller\ResultFactory; -class Edit extends RatingController +class Edit extends RatingController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Review/Controller/Adminhtml/Rating/Index.php b/app/code/Magento/Review/Controller/Adminhtml/Rating/Index.php index b719a299505..ff9ab4d50ea 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Rating/Index.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Rating/Index.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Rating; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Rating as RatingController; use Magento\Framework\Controller\ResultFactory; -class Index extends RatingController +class Index extends RatingController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Review/Controller/Adminhtml/Rating/NewAction.php b/app/code/Magento/Review/Controller/Adminhtml/Rating/NewAction.php index 18ff73ad31c..92d20aeec3e 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Rating/NewAction.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Rating/NewAction.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Rating; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Review\Controller\Adminhtml\Rating as RatingController; use Magento\Framework\Controller\ResultFactory; -class NewAction extends RatingController +class NewAction extends RatingController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Forward diff --git a/app/code/Magento/Review/Controller/Adminhtml/Rating/Save.php b/app/code/Magento/Review/Controller/Adminhtml/Rating/Save.php index 62cca9c824e..5dd464f7eb6 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Rating/Save.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Rating/Save.php @@ -5,10 +5,11 @@ */ namespace Magento\Review\Controller\Adminhtml\Rating; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Review\Controller\Adminhtml\Rating as RatingController; use Magento\Framework\Controller\ResultFactory; -class Save extends RatingController +class Save extends RatingController implements HttpPostActionInterface { /** * Save rating diff --git a/app/code/Magento/Review/Controller/Product/ListAjax.php b/app/code/Magento/Review/Controller/Product/ListAjax.php index a309b5f0626..d923814c7dc 100644 --- a/app/code/Magento/Review/Controller/Product/ListAjax.php +++ b/app/code/Magento/Review/Controller/Product/ListAjax.php @@ -11,8 +11,9 @@ use Magento\Framework\View\Result\Layout; use Magento\Review\Controller\Product as ProductController; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; -class ListAjax extends ProductController +class ListAjax extends ProductController implements HttpGetActionInterface { /** * Show list of product's reviews diff --git a/app/code/Magento/Review/Controller/Product/Post.php b/app/code/Magento/Review/Controller/Product/Post.php index be18f8fe25b..32838eb6acb 100644 --- a/app/code/Magento/Review/Controller/Product/Post.php +++ b/app/code/Magento/Review/Controller/Product/Post.php @@ -5,11 +5,12 @@ */ namespace Magento\Review\Controller\Product; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Review\Controller\Product as ProductController; use Magento\Framework\Controller\ResultFactory; use Magento\Review\Model\Review; -class Post extends ProductController +class Post extends ProductController implements HttpPostActionInterface { /** * Submit new review action diff --git a/app/code/Magento/Review/etc/db_schema.xml b/app/code/Magento/Review/etc/db_schema.xml index 1a2ff588180..2ae43ea608e 100644 --- a/app/code/Magento/Review/etc/db_schema.xml +++ b/app/code/Magento/Review/etc/db_schema.xml @@ -95,7 +95,7 @@ + default="0" comment="Store ID"/> @@ -108,9 +108,9 @@
+ comment="Review ID"/> + comment="Store ID"/> @@ -125,7 +125,7 @@
+ comment="Entity ID"/> @@ -136,9 +136,9 @@
+ comment="Rating ID"/> + default="0" comment="Entity ID"/> diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php index 1912655a929..10b80b6f4e5 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/History.php @@ -88,7 +88,8 @@ public function getStatuses() */ public function canSendCommentEmail() { - return $this->_salesData->canSendOrderCommentEmail($this->getOrder()->getStore()->getId()); + return $this->_salesData->canSendOrderCommentEmail($this->getOrder()->getStore()->getId()) + && $this->_authorization->isAllowed('Magento_Sales::email'); } /** diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php index 64b53d10d4a..0972d743142 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php @@ -61,6 +61,7 @@ public function getOrder() /** * Compose and get order full history. + * * Consists of the status history comments as well as of invoices, shipments and creditmemos creations * * @TODO This method requires refactoring. Need to create separate model for comment history handling @@ -218,7 +219,7 @@ protected function _prepareHistoryItem($label, $notified, $created, $comment = ' } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabLabel() { @@ -226,7 +227,7 @@ public function getTabLabel() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTabTitle() { @@ -264,7 +265,7 @@ public function getTabUrl() } /** - * {@inheritdoc} + * @inheritdoc */ public function canShowTab() { @@ -272,7 +273,7 @@ public function canShowTab() } /** - * {@inheritdoc} + * @inheritdoc */ public function isHidden() { @@ -303,11 +304,7 @@ public static function sortHistoryByTimestamp($a, $b) $createdAtA = $a['created_at']; $createdAtB = $b['created_at']; - /** @var $createdAtA \DateTime */ - if ($createdAtA->getTimestamp() == $createdAtB->getTimestamp()) { - return 0; - } - return $createdAtA->getTimestamp() < $createdAtB->getTimestamp() ? -1 : 1; + return $createdAtA->getTimestamp() <=> $createdAtB->getTimestamp(); } /** diff --git a/app/code/Magento/Sales/Block/Status/Grid/Column/Unassign.php b/app/code/Magento/Sales/Block/Status/Grid/Column/Unassign.php index b413951d9d4..f989deb0ae7 100644 --- a/app/code/Magento/Sales/Block/Status/Grid/Column/Unassign.php +++ b/app/code/Magento/Sales/Block/Status/Grid/Column/Unassign.php @@ -5,12 +5,35 @@ */ namespace Magento\Sales\Block\Status\Grid\Column; +use Magento\Framework\App\ObjectManager; +use \Magento\Backend\Block\Template\Context; +use Magento\Framework\Serialize\Serializer\Json; + /** * @api * @since 100.0.2 */ class Unassign extends \Magento\Backend\Block\Widget\Grid\Column { + /** + * @var Json + */ + private $json; + + /** + * @inheritDoc + * + * @param Json|null $json + */ + public function __construct( + Context $context, + array $data = [], + ?Json $json = null + ) { + parent::__construct($context, $data); + $this->json = $json ?? ObjectManager::getInstance()->get(Json::class); + } + /** * Add decorated action to column * @@ -36,9 +59,16 @@ public function decorateAction($value, $row, $column, $isExport) $cell = ''; $state = $row->getState(); if (!empty($state)) { - $url = $this->getUrl('*/*/unassign', ['status' => $row->getStatus(), 'state' => $row->getState()]); + $url = $this->getUrl('*/*/unassign'); $label = __('Unassign'); - $cell = '' . $label . ''; + $cell = '' . $label . ''; } return $cell; } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/Index.php index b29c1ea3700..165c9e894de 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/Index.php @@ -5,7 +5,10 @@ */ namespace Magento\Sales\Controller\Adminhtml\Creditmemo; -class Index extends \Magento\Sales\Controller\Adminhtml\Creditmemo\AbstractCreditmemo\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Sales\Controller\Adminhtml\Creditmemo\AbstractCreditmemo\Index as AbstractIndex; + +class Index extends AbstractIndex implements HttpGetActionInterface { /** * Index page diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/Index.php index 429a388d939..bc53f752801 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/Index.php @@ -6,6 +6,8 @@ */ namespace Magento\Sales\Controller\Adminhtml\Invoice; -class Index extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\Index implements HttpGetActionInterface { } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/View.php b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/View.php index 62add99ab59..3b521999432 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/View.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/View.php @@ -6,6 +6,8 @@ */ namespace Magento\Sales\Controller\Adminhtml\Invoice; -class View extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class View extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View implements HttpGetActionInterface { } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php index 7e41c7417b3..f96d221a7d3 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php @@ -6,7 +6,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; -class Cancel extends \Magento\Sales\Controller\Adminhtml\Order +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Cancel extends \Magento\Sales\Controller\Adminhtml\Order implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php index 8496c4f28d8..4acef74b810 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/CommentsHistory.php @@ -6,16 +6,20 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Sales\Api\OrderManagementInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Psr\Log\LoggerInterface; +use Magento\Sales\Controller\Adminhtml\Order as OrderAction; /** - * Class CommentsHistory + * Comments History tab, needs to be accessible by POST becuase of tabs mechanism. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CommentsHistory extends \Magento\Sales\Controller\Adminhtml\Order +class CommentsHistory extends OrderAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Framework\View\LayoutFactory diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php index ce3a36729de..035dc787789 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php @@ -5,7 +5,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Create; -class Index extends \Magento\Sales\Controller\Adminhtml\Order\Create +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Sales\Controller\Adminhtml\Order\Create implements HttpGetActionInterface { /** * Index page diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php index bc4eb3cfba4..1e13e282cae 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php @@ -5,12 +5,15 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Create; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Backend\Model\View\Result\ForwardFactory; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\Controller\Result\RawFactory; +use Magento\Sales\Controller\Adminhtml\Order\Create as CreateAction; -class LoadBlock extends \Magento\Sales\Controller\Adminhtml\Order\Create +class LoadBlock extends CreateAction implements HttpPostActionInterface, HttpGetActionInterface { /** * @var RawFactory diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php index 929e23075e0..4348984d040 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php @@ -6,9 +6,10 @@ namespace Magento\Sales\Controller\Adminhtml\Order\Create; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\PaymentException; -class Save extends \Magento\Sales\Controller\Adminhtml\Order\Create +class Save extends \Magento\Sales\Controller\Adminhtml\Order\Create implements HttpPostActionInterface { /** * Saving quote and create order diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/ShowUpdateResult.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/ShowUpdateResult.php index 01119c3aa88..24aedf56ec5 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/ShowUpdateResult.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/ShowUpdateResult.php @@ -5,12 +5,13 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Create; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Backend\Model\View\Result\ForwardFactory; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\Controller\Result\RawFactory; -class ShowUpdateResult extends \Magento\Sales\Controller\Adminhtml\Order\Create +class ShowUpdateResult extends \Magento\Sales\Controller\Adminhtml\Order\Create implements HttpGetActionInterface { /** * @var RawFactory diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Start.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Start.php index ee88b3361cc..bc75bc4a578 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Start.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Start.php @@ -5,9 +5,10 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Create; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; -class Start extends \Magento\Sales\Controller\Adminhtml\Order\Create +class Start extends \Magento\Sales\Controller\Adminhtml\Order\Create implements HttpGetActionInterface { /** * Start order create action diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/NewAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/NewAction.php index 03910f2eccc..3ac3abda82c 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/NewAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/NewAction.php @@ -5,9 +5,10 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; -class NewAction extends \Magento\Backend\App\Action +class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php index 2a2445d2b2a..91ae3b7d4e0 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php @@ -5,11 +5,12 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender; -class Save extends \Magento\Backend\App\Action +class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Start.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Start.php index 10ba7e425b6..3dc4aa6dcd5 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Start.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Start.php @@ -5,7 +5,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; -class Start extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Start extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/UpdateQty.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/UpdateQty.php index bfd95666e7c..d49fa8b8dc6 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/UpdateQty.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/UpdateQty.php @@ -5,9 +5,10 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; -class UpdateQty extends \Magento\Backend\App\Action +class UpdateQty extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Index.php index 13a86befec0..d5dc1171923 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Index.php @@ -5,7 +5,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; -class Index extends \Magento\Sales\Controller\Adminhtml\Order +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Sales\Controller\Adminhtml\Order implements HttpGetActionInterface { /** * Orders grid diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php index 2d7826807a6..3295b244f32 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php @@ -7,12 +7,13 @@ namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Framework\Registry; use Magento\Framework\View\Result\PageFactory; use Magento\Sales\Model\Service\InvoiceService; -class NewAction extends \Magento\Backend\App\Action +class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php index 71338c818c4..ab74a64b6fc 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php @@ -7,6 +7,7 @@ namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; @@ -19,7 +20,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Backend\App\Action +class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Start.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Start.php index ca97ee3b334..4648656a325 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Start.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Start.php @@ -6,7 +6,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; -class Start extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Start extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View implements HttpGetActionInterface { /** * Start create invoice action diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/UpdateQty.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/UpdateQty.php index 78fb4aff3f2..c94afa3cc80 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/UpdateQty.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/UpdateQty.php @@ -7,20 +7,21 @@ namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; -use Magento\Backend\App\Action; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\Controller\Result\RawFactory; use Magento\Backend\App\Action\Context; use Magento\Framework\Registry; use Magento\Sales\Model\Service\InvoiceService; +use Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View as AbstractView; /** * Class UpdateQty * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class UpdateQty extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View +class UpdateQty extends AbstractView implements HttpPostActionInterface { /** * @var JsonFactory diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/View.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/View.php index 78a413d5636..da700aae2f7 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/View.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/View.php @@ -6,11 +6,12 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\View\Result\PageFactory; use Magento\Backend\App\Action\Context; use Magento\Framework\Registry; -class View extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View +class View extends \Magento\Sales\Controller\Adminhtml\Invoice\AbstractInvoice\View implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php index 32cf6dbfc7c..deb20a989a2 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php @@ -5,13 +5,14 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; use Magento\Sales\Api\OrderManagementInterface; -class MassCancel extends \Magento\Sales\Controller\Adminhtml\Order\AbstractMassAction +class MassCancel extends \Magento\Sales\Controller\Adminhtml\Order\AbstractMassAction implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Assign.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Assign.php index 982ee53cb0f..211ded865b4 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Assign.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Assign.php @@ -6,11 +6,12 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Status; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Registry; use Magento\Backend\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class Assign extends \Magento\Sales\Controller\Adminhtml\Order\Status +class Assign extends \Magento\Sales\Controller\Adminhtml\Order\Status implements HttpGetActionInterface { /** * @var PageFactory diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php index 3b98d206d5f..28a8e1cb02a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php @@ -6,7 +6,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Status; -class AssignPost extends \Magento\Sales\Controller\Adminhtml\Order\Status +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class AssignPost extends \Magento\Sales\Controller\Adminhtml\Order\Status implements HttpPostActionInterface { /** * Save status assignment to state diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Index.php index 110402ee270..3755dca9da9 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Index.php @@ -6,11 +6,12 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Status; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Registry; use Magento\Backend\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class Index extends \Magento\Sales\Controller\Adminhtml\Order\Status +class Index extends \Magento\Sales\Controller\Adminhtml\Order\Status implements HttpGetActionInterface { /** * @var PageFactory diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php index bd0ad771815..4645588a752 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php @@ -6,25 +6,32 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Status; -class Save extends \Magento\Sales\Controller\Adminhtml\Order\Status +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\Filter\FilterManager; +use Magento\Sales\Model\Order\Status; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Sales\Controller\Adminhtml\Order\Status as StatusAction; + +class Save extends StatusAction implements HttpPostActionInterface { /** * Save status form processing * - * @return \Magento\Backend\Model\View\Result\Redirect + * @return Redirect */ public function execute() { $data = $this->getRequest()->getPostValue(); $isNew = $this->getRequest()->getParam('is_new'); - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); if ($data) { $statusCode = $this->getRequest()->getParam('status'); //filter tags in labels/status - /** @var $filterManager \Magento\Framework\Filter\FilterManager */ - $filterManager = $this->_objectManager->get(\Magento\Framework\Filter\FilterManager::class); + /** @var $filterManager FilterManager */ + $filterManager = $this->_objectManager->get(FilterManager::class); if ($isNew) { $statusCode = $data['status'] = $filterManager->stripTags($data['status']); } @@ -37,7 +44,7 @@ public function execute() $label = $filterManager->stripTags($label); } - $status = $this->_objectManager->create(\Magento\Sales\Model\Order\Status::class)->load($statusCode); + $status = $this->_objectManager->create(Status::class)->load($statusCode); // check if status exist if ($isNew && $status->getStatus()) { $this->messageManager @@ -52,7 +59,7 @@ public function execute() $status->save(); $this->messageManager->addSuccessMessage(__('You saved the order status.')); return $resultRedirect->setPath('sales/*/'); - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addExceptionMessage( @@ -67,11 +74,11 @@ public function execute() } /** - * @param \Magento\Backend\Model\View\Result\Redirect $resultRedirect + * @param Redirect $resultRedirect * @param bool $isNew - * @return \Magento\Backend\Model\View\Result\Redirect + * @return Redirect */ - private function getRedirect(\Magento\Backend\Model\View\Result\Redirect $resultRedirect, $isNew) + private function getRedirect(Redirect $resultRedirect, $isNew) { if ($isNew) { return $resultRedirect->setPath('sales/*/new'); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php index 2723d483dd3..16bb275a88e 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php @@ -6,7 +6,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order\Status; -class Unassign extends \Magento\Sales\Controller\Adminhtml\Order\Status +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Unassign extends \Magento\Sales\Controller\Adminhtml\Order\Status implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Shipment/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Shipment/Index.php index c7f560c52fb..59d8a68a744 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Shipment/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Shipment/Index.php @@ -6,6 +6,9 @@ */ namespace Magento\Sales\Controller\Adminhtml\Shipment; -class Index extends \Magento\Sales\Controller\Adminhtml\Shipment\AbstractShipment\Index +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Sales\Controller\Adminhtml\Shipment\AbstractShipment\Index as AbstractIndex; + +class Index extends AbstractIndex implements HttpGetActionInterface { } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Index.php index 750781e9919..a7327050064 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Index.php @@ -6,9 +6,10 @@ */ namespace Magento\Sales\Controller\Adminhtml\Transactions; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\Model\View\Result\Page; -class Index extends \Magento\Sales\Controller\Adminhtml\Transactions +class Index extends \Magento\Sales\Controller\Adminhtml\Transactions implements HttpGetActionInterface { /** * @return Page diff --git a/app/code/Magento/Sales/Controller/Order/Creditmemo.php b/app/code/Magento/Sales/Controller/Order/Creditmemo.php index 2a7fd3e9849..74aae3e1f84 100644 --- a/app/code/Magento/Sales/Controller/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Controller/Order/Creditmemo.php @@ -6,8 +6,10 @@ */ namespace Magento\Sales\Controller\Order; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Sales\Controller\OrderInterface; +use Magento\Sales\Controller\AbstractController\Creditmemo as AbstractCreditmemo; -class Creditmemo extends \Magento\Sales\Controller\AbstractController\Creditmemo implements OrderInterface +class Creditmemo extends AbstractCreditmemo implements OrderInterface, HttpGetActionInterface { } diff --git a/app/code/Magento/Sales/Controller/Order/History.php b/app/code/Magento/Sales/Controller/Order/History.php index 23e8208d8b1..37de1598458 100644 --- a/app/code/Magento/Sales/Controller/Order/History.php +++ b/app/code/Magento/Sales/Controller/Order/History.php @@ -6,11 +6,12 @@ */ namespace Magento\Sales\Controller\Order; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Sales\Controller\OrderInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; -class History extends \Magento\Framework\App\Action\Action implements OrderInterface +class History extends \Magento\Framework\App\Action\Action implements OrderInterface, HttpGetActionInterface { /** * @var PageFactory diff --git a/app/code/Magento/Sales/Controller/Order/View.php b/app/code/Magento/Sales/Controller/Order/View.php index d68b9481c86..21a5706a444 100644 --- a/app/code/Magento/Sales/Controller/Order/View.php +++ b/app/code/Magento/Sales/Controller/Order/View.php @@ -6,8 +6,9 @@ */ namespace Magento\Sales\Controller\Order; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Sales\Controller\OrderInterface; -class View extends \Magento\Sales\Controller\AbstractController\View implements OrderInterface +class View extends \Magento\Sales\Controller\AbstractController\View implements OrderInterface, HttpGetActionInterface { } diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index 7372d9715c7..9f30342ed33 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -16,7 +16,7 @@ use Magento\Sales\Model\ResourceModel\Order\Address\Collection; use Magento\Sales\Model\ResourceModel\Order\Creditmemo\Collection as CreditmemoCollection; use Magento\Sales\Model\ResourceModel\Order\Invoice\Collection as InvoiceCollection; -use Magento\Sales\Model\ResourceModel\Order\Item\Collection as ImportCollection; +use Magento\Sales\Model\ResourceModel\Order\Item\Collection as ItemCollection; use Magento\Sales\Model\ResourceModel\Order\Payment\Collection as PaymentCollection; use Magento\Sales\Model\ResourceModel\Order\Shipment\Collection as ShipmentCollection; use Magento\Sales\Model\ResourceModel\Order\Shipment\Track\Collection as TrackCollection; @@ -564,6 +564,7 @@ public function canCancel() /** * Getter whether the payment can be voided + * * @return bool */ public function canVoidPayment() @@ -880,7 +881,7 @@ protected function _placePayment() } /** - * {@inheritdoc} + * @inheritdoc */ public function getPayment() { @@ -1008,6 +1009,7 @@ public function addStatusToHistory($status, $comment = '', $isCustomerNotified = /** * Add a comment to order + * * Different or default status may be specified * * @param string $comment @@ -1023,6 +1025,7 @@ public function addStatusHistoryComment($comment, $status = false) /** * Add a comment to order status history + * * Different or default status may be specified * * @param string $comment @@ -1088,6 +1091,8 @@ public function place() } /** + * Hold order + * * @return $this * @throws \Magento\Framework\Exception\LocalizedException */ @@ -1233,6 +1238,8 @@ public function getShippingMethod($asObject = false) /*********************** ADDRESSES ***************************/ /** + * Returns address collection instance + * * @return Collection */ public function getAddressesCollection() @@ -1247,6 +1254,8 @@ public function getAddressesCollection() } /** + * Returns address by id + * * @param mixed $addressId * @return false */ @@ -1261,6 +1270,8 @@ public function getAddressById($addressId) } /** + * Add address to order + * * @param \Magento\Sales\Model\Order\Address $address * @return $this */ @@ -1275,9 +1286,11 @@ public function addAddress(\Magento\Sales\Model\Order\Address $address) } /** + * Returns items collection + * * @param array $filterByTypes * @param bool $nonChildrenOnly - * @return ImportCollection + * @return ItemCollection */ public function getItemsCollection($filterByTypes = [], $nonChildrenOnly = false) { @@ -1302,7 +1315,7 @@ public function getItemsCollection($filterByTypes = [], $nonChildrenOnly = false * Get random items collection without related children * * @param int $limit - * @return ImportCollection + * @return ItemCollection */ public function getParentItemsRandomCollection($limit = 1) { @@ -1314,7 +1327,7 @@ public function getParentItemsRandomCollection($limit = 1) * * @param int $limit * @param bool $nonChildrenOnly - * @return ImportCollection + * @return ItemCollection */ protected function _getItemsRandomCollection($limit, $nonChildrenOnly = false) { @@ -1347,6 +1360,8 @@ protected function _getItemsRandomCollection($limit, $nonChildrenOnly = false) } /** + * Returns all order items + * * @return \Magento\Sales\Model\Order\Item[] */ public function getAllItems() @@ -1361,6 +1376,8 @@ public function getAllItems() } /** + * Returns all visible items + * * @return array */ public function getAllVisibleItems() @@ -1392,6 +1409,8 @@ public function getItemById($itemId) } /** + * Returns Item By QuoteItem Id + * * @param mixed $quoteItemId * @return \Magento\Framework\DataObject|null */ @@ -1406,6 +1425,8 @@ public function getItemByQuoteItemId($quoteItemId) } /** + * Add item to order + * * @param \Magento\Sales\Model\Order\Item $item * @return $this */ @@ -1421,6 +1442,8 @@ public function addItem(\Magento\Sales\Model\Order\Item $item) /*********************** PAYMENTS ***************************/ /** + * Returns payment collection + * * @return PaymentCollection */ public function getPaymentsCollection() @@ -1435,6 +1458,8 @@ public function getPaymentsCollection() } /** + * Returns all payments + * * @return array */ public function getAllPayments() @@ -1449,6 +1474,8 @@ public function getAllPayments() } /** + * Returns payment by id + * * @param mixed $paymentId * @return Payment|false */ @@ -1463,7 +1490,7 @@ public function getPaymentById($paymentId) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPayment(\Magento\Sales\Api\Data\OrderPaymentInterface $payment = null) { @@ -1530,6 +1557,8 @@ public function getVisibleStatusHistory() } /** + * Returns status history by id + * * @param mixed $statusId * @return string|false */ @@ -1545,6 +1574,7 @@ public function getStatusHistoryById($statusId) /** * Set the order status history object and the order object to each other + * * Adds the object to the status history collection, which is automatically saved when the order is saved. * See the entity_id attribute backend model. * Or the history record can be saved standalone after this. @@ -1564,6 +1594,8 @@ public function addStatusHistory(\Magento\Sales\Model\Order\Status\History $hist } /** + * Returns real order id + * * @return string */ public function getRealOrderId() @@ -1592,9 +1624,9 @@ public function getOrderCurrency() /** * Get formatted price value including order currency rate to order website currency * - * @param float $price - * @param bool $addBrackets - * @return string + * @param float $price + * @param bool $addBrackets + * @return string */ public function formatPrice($price, $addBrackets = false) { @@ -1602,6 +1634,8 @@ public function formatPrice($price, $addBrackets = false) } /** + * Format price precision + * * @param float $price * @param int $precision * @param bool $addBrackets @@ -1615,8 +1649,8 @@ public function formatPricePrecision($price, $precision, $addBrackets = false) /** * Retrieve text formatted price value including order rate * - * @param float $price - * @return string + * @param float $price + * @return string */ public function formatPriceTxt($price) { @@ -1637,6 +1671,8 @@ public function getBaseCurrency() } /** + * Format base price + * * @param float $price * @return string */ @@ -1646,6 +1682,8 @@ public function formatBasePrice($price) } /** + * Format Base Price Precision + * * @param float $price * @param int $precision * @return string @@ -1656,6 +1694,8 @@ public function formatBasePricePrecision($price, $precision) } /** + * Is Currency Different + * * @return bool */ public function isCurrencyDifferent() @@ -1688,6 +1728,8 @@ public function getBaseTotalDue() } /** + * Returns object data + * * @param string $key * @param null|string|int $index * @return mixed @@ -1828,6 +1870,8 @@ public function getRelatedObjects() } /** + * Returns customer name + * * @return string */ public function getCustomerName() @@ -1855,8 +1899,8 @@ public function addRelatedObject(\Magento\Framework\Model\AbstractModel $object) /** * Get formatted order created date in store timezone * - * @param string $format date format type (short|medium|long|full) - * @return string + * @param string $format date format type (short|medium|long|full) + * @return string */ public function getCreatedAtFormatted($format) { @@ -1870,6 +1914,8 @@ public function getCreatedAtFormatted($format) } /** + * Returns email customer note + * * @return string */ public function getEmailCustomerNote() @@ -1881,6 +1927,8 @@ public function getEmailCustomerNote() } /** + * Returns store group name + * * @return string */ public function getStoreGroupName() @@ -1894,7 +1942,8 @@ public function getStoreGroupName() /** * Resets all data in object - * so after another load it will be complete new object + * + * So after another load it will be complete new object * * @return $this */ @@ -1918,6 +1967,8 @@ public function reset() } /** + * Get order is not virtual + * * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ @@ -1960,6 +2011,8 @@ public function getIncrementId() } /** + * Returns order items + * * @return \Magento\Sales\Api\Data\OrderItemInterface[] */ public function getItems() @@ -1974,7 +2027,7 @@ public function getItems() } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function setItems($items) @@ -1983,6 +2036,8 @@ public function setItems($items) } /** + * Returns order addresses + * * @return \Magento\Sales\Api\Data\OrderAddressInterface[] */ public function getAddresses() @@ -1997,6 +2052,8 @@ public function getAddresses() } /** + * Returns status history + * * @return \Magento\Sales\Api\Data\OrderStatusHistoryInterface[]|null */ public function getStatusHistories() @@ -2011,7 +2068,7 @@ public function getStatusHistories() } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Sales\Api\Data\OrderExtensionInterface|null */ @@ -2021,7 +2078,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Sales\Api\Data\OrderExtensionInterface $extensionAttributes * @return $this @@ -2504,7 +2561,7 @@ public function getCreatedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCreatedAt($createdAt) { @@ -3322,7 +3379,7 @@ public function getXForwardedFor() } /** - * {@inheritdoc} + * @inheritdoc */ public function setStatusHistories(array $statusHistories = null) { @@ -3330,7 +3387,7 @@ public function setStatusHistories(array $statusHistories = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStatus($status) { @@ -3338,7 +3395,7 @@ public function setStatus($status) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCouponCode($code) { @@ -3346,7 +3403,7 @@ public function setCouponCode($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function setProtectCode($code) { @@ -3354,7 +3411,7 @@ public function setProtectCode($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingDescription($description) { @@ -3362,7 +3419,7 @@ public function setShippingDescription($description) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsVirtual($isVirtual) { @@ -3370,7 +3427,7 @@ public function setIsVirtual($isVirtual) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreId($id) { @@ -3378,7 +3435,7 @@ public function setStoreId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerId($id) { @@ -3386,7 +3443,7 @@ public function setCustomerId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountAmount($amount) { @@ -3394,7 +3451,7 @@ public function setBaseDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountCanceled($baseDiscountCanceled) { @@ -3402,7 +3459,7 @@ public function setBaseDiscountCanceled($baseDiscountCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountInvoiced($baseDiscountInvoiced) { @@ -3410,7 +3467,7 @@ public function setBaseDiscountInvoiced($baseDiscountInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountRefunded($baseDiscountRefunded) { @@ -3418,7 +3475,7 @@ public function setBaseDiscountRefunded($baseDiscountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseGrandTotal($amount) { @@ -3426,7 +3483,7 @@ public function setBaseGrandTotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingAmount($amount) { @@ -3434,7 +3491,7 @@ public function setBaseShippingAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingCanceled($baseShippingCanceled) { @@ -3442,7 +3499,7 @@ public function setBaseShippingCanceled($baseShippingCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingInvoiced($baseShippingInvoiced) { @@ -3450,7 +3507,7 @@ public function setBaseShippingInvoiced($baseShippingInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingRefunded($baseShippingRefunded) { @@ -3458,7 +3515,7 @@ public function setBaseShippingRefunded($baseShippingRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingTaxAmount($amount) { @@ -3466,7 +3523,7 @@ public function setBaseShippingTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingTaxRefunded($baseShippingTaxRefunded) { @@ -3474,7 +3531,7 @@ public function setBaseShippingTaxRefunded($baseShippingTaxRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseSubtotal($amount) { @@ -3482,7 +3539,7 @@ public function setBaseSubtotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseSubtotalCanceled($baseSubtotalCanceled) { @@ -3490,7 +3547,7 @@ public function setBaseSubtotalCanceled($baseSubtotalCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseSubtotalInvoiced($baseSubtotalInvoiced) { @@ -3498,7 +3555,7 @@ public function setBaseSubtotalInvoiced($baseSubtotalInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseSubtotalRefunded($baseSubtotalRefunded) { @@ -3506,7 +3563,7 @@ public function setBaseSubtotalRefunded($baseSubtotalRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxAmount($amount) { @@ -3514,7 +3571,7 @@ public function setBaseTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxCanceled($baseTaxCanceled) { @@ -3522,7 +3579,7 @@ public function setBaseTaxCanceled($baseTaxCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxInvoiced($baseTaxInvoiced) { @@ -3530,7 +3587,7 @@ public function setBaseTaxInvoiced($baseTaxInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxRefunded($baseTaxRefunded) { @@ -3538,7 +3595,7 @@ public function setBaseTaxRefunded($baseTaxRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseToGlobalRate($rate) { @@ -3546,7 +3603,7 @@ public function setBaseToGlobalRate($rate) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseToOrderRate($rate) { @@ -3554,7 +3611,7 @@ public function setBaseToOrderRate($rate) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalCanceled($baseTotalCanceled) { @@ -3562,7 +3619,7 @@ public function setBaseTotalCanceled($baseTotalCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalInvoiced($baseTotalInvoiced) { @@ -3570,7 +3627,7 @@ public function setBaseTotalInvoiced($baseTotalInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalInvoicedCost($baseTotalInvoicedCost) { @@ -3578,7 +3635,7 @@ public function setBaseTotalInvoicedCost($baseTotalInvoicedCost) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalOfflineRefunded($baseTotalOfflineRefunded) { @@ -3586,7 +3643,7 @@ public function setBaseTotalOfflineRefunded($baseTotalOfflineRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalOnlineRefunded($baseTotalOnlineRefunded) { @@ -3594,7 +3651,7 @@ public function setBaseTotalOnlineRefunded($baseTotalOnlineRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalPaid($baseTotalPaid) { @@ -3602,7 +3659,7 @@ public function setBaseTotalPaid($baseTotalPaid) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalQtyOrdered($baseTotalQtyOrdered) { @@ -3610,7 +3667,7 @@ public function setBaseTotalQtyOrdered($baseTotalQtyOrdered) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalRefunded($baseTotalRefunded) { @@ -3618,7 +3675,7 @@ public function setBaseTotalRefunded($baseTotalRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountAmount($amount) { @@ -3626,7 +3683,7 @@ public function setDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountCanceled($discountCanceled) { @@ -3634,7 +3691,7 @@ public function setDiscountCanceled($discountCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountInvoiced($discountInvoiced) { @@ -3642,7 +3699,7 @@ public function setDiscountInvoiced($discountInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountRefunded($discountRefunded) { @@ -3650,7 +3707,7 @@ public function setDiscountRefunded($discountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGrandTotal($amount) { @@ -3658,7 +3715,7 @@ public function setGrandTotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingAmount($amount) { @@ -3666,7 +3723,7 @@ public function setShippingAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingCanceled($shippingCanceled) { @@ -3674,7 +3731,7 @@ public function setShippingCanceled($shippingCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingInvoiced($shippingInvoiced) { @@ -3682,7 +3739,7 @@ public function setShippingInvoiced($shippingInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingRefunded($shippingRefunded) { @@ -3690,7 +3747,7 @@ public function setShippingRefunded($shippingRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingTaxAmount($amount) { @@ -3698,7 +3755,7 @@ public function setShippingTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingTaxRefunded($shippingTaxRefunded) { @@ -3706,7 +3763,7 @@ public function setShippingTaxRefunded($shippingTaxRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreToBaseRate($rate) { @@ -3714,7 +3771,7 @@ public function setStoreToBaseRate($rate) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreToOrderRate($rate) { @@ -3722,7 +3779,7 @@ public function setStoreToOrderRate($rate) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubtotal($amount) { @@ -3730,7 +3787,7 @@ public function setSubtotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubtotalCanceled($subtotalCanceled) { @@ -3738,7 +3795,7 @@ public function setSubtotalCanceled($subtotalCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubtotalInvoiced($subtotalInvoiced) { @@ -3746,7 +3803,7 @@ public function setSubtotalInvoiced($subtotalInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubtotalRefunded($subtotalRefunded) { @@ -3754,7 +3811,7 @@ public function setSubtotalRefunded($subtotalRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxAmount($amount) { @@ -3762,7 +3819,7 @@ public function setTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxCanceled($taxCanceled) { @@ -3770,7 +3827,7 @@ public function setTaxCanceled($taxCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxInvoiced($taxInvoiced) { @@ -3778,7 +3835,7 @@ public function setTaxInvoiced($taxInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxRefunded($taxRefunded) { @@ -3786,7 +3843,7 @@ public function setTaxRefunded($taxRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalCanceled($totalCanceled) { @@ -3794,7 +3851,7 @@ public function setTotalCanceled($totalCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalInvoiced($totalInvoiced) { @@ -3802,7 +3859,7 @@ public function setTotalInvoiced($totalInvoiced) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalOfflineRefunded($totalOfflineRefunded) { @@ -3810,7 +3867,7 @@ public function setTotalOfflineRefunded($totalOfflineRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalOnlineRefunded($totalOnlineRefunded) { @@ -3818,7 +3875,7 @@ public function setTotalOnlineRefunded($totalOnlineRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalPaid($totalPaid) { @@ -3826,7 +3883,7 @@ public function setTotalPaid($totalPaid) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalQtyOrdered($totalQtyOrdered) { @@ -3834,7 +3891,7 @@ public function setTotalQtyOrdered($totalQtyOrdered) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalRefunded($totalRefunded) { @@ -3842,7 +3899,7 @@ public function setTotalRefunded($totalRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCanShipPartially($flag) { @@ -3850,7 +3907,7 @@ public function setCanShipPartially($flag) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCanShipPartiallyItem($flag) { @@ -3858,7 +3915,7 @@ public function setCanShipPartiallyItem($flag) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerIsGuest($customerIsGuest) { @@ -3866,7 +3923,7 @@ public function setCustomerIsGuest($customerIsGuest) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerNoteNotify($customerNoteNotify) { @@ -3874,7 +3931,7 @@ public function setCustomerNoteNotify($customerNoteNotify) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBillingAddressId($id) { @@ -3882,7 +3939,7 @@ public function setBillingAddressId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerGroupId($id) { @@ -3890,7 +3947,7 @@ public function setCustomerGroupId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEditIncrement($editIncrement) { @@ -3898,7 +3955,7 @@ public function setEditIncrement($editIncrement) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEmailSent($emailSent) { @@ -3906,7 +3963,7 @@ public function setEmailSent($emailSent) } /** - * {@inheritdoc} + * @inheritdoc */ public function setForcedShipmentWithInvoice($forcedShipmentWithInvoice) { @@ -3914,7 +3971,7 @@ public function setForcedShipmentWithInvoice($forcedShipmentWithInvoice) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPaymentAuthExpiration($paymentAuthExpiration) { @@ -3922,7 +3979,7 @@ public function setPaymentAuthExpiration($paymentAuthExpiration) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQuoteAddressId($id) { @@ -3930,7 +3987,7 @@ public function setQuoteAddressId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQuoteId($id) { @@ -3938,7 +3995,7 @@ public function setQuoteId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAdjustmentNegative($adjustmentNegative) { @@ -3946,7 +4003,7 @@ public function setAdjustmentNegative($adjustmentNegative) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAdjustmentPositive($adjustmentPositive) { @@ -3954,7 +4011,7 @@ public function setAdjustmentPositive($adjustmentPositive) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAdjustmentNegative($baseAdjustmentNegative) { @@ -3962,7 +4019,7 @@ public function setBaseAdjustmentNegative($baseAdjustmentNegative) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAdjustmentPositive($baseAdjustmentPositive) { @@ -3970,7 +4027,7 @@ public function setBaseAdjustmentPositive($baseAdjustmentPositive) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingDiscountAmount($amount) { @@ -3978,7 +4035,7 @@ public function setBaseShippingDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseSubtotalInclTax($amount) { @@ -3986,7 +4043,7 @@ public function setBaseSubtotalInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTotalDue($baseTotalDue) { @@ -3994,7 +4051,7 @@ public function setBaseTotalDue($baseTotalDue) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPaymentAuthorizationAmount($amount) { @@ -4002,7 +4059,7 @@ public function setPaymentAuthorizationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingDiscountAmount($amount) { @@ -4010,7 +4067,7 @@ public function setShippingDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubtotalInclTax($amount) { @@ -4018,7 +4075,7 @@ public function setSubtotalInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalDue($totalDue) { @@ -4026,7 +4083,7 @@ public function setTotalDue($totalDue) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeight($weight) { @@ -4034,7 +4091,7 @@ public function setWeight($weight) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerDob($customerDob) { @@ -4042,7 +4099,7 @@ public function setCustomerDob($customerDob) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIncrementId($id) { @@ -4050,7 +4107,7 @@ public function setIncrementId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAppliedRuleIds($appliedRuleIds) { @@ -4058,7 +4115,7 @@ public function setAppliedRuleIds($appliedRuleIds) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseCurrencyCode($code) { @@ -4066,7 +4123,7 @@ public function setBaseCurrencyCode($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerEmail($customerEmail) { @@ -4074,7 +4131,7 @@ public function setCustomerEmail($customerEmail) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerFirstname($customerFirstname) { @@ -4082,7 +4139,7 @@ public function setCustomerFirstname($customerFirstname) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerLastname($customerLastname) { @@ -4090,7 +4147,7 @@ public function setCustomerLastname($customerLastname) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerMiddlename($customerMiddlename) { @@ -4098,7 +4155,7 @@ public function setCustomerMiddlename($customerMiddlename) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerPrefix($customerPrefix) { @@ -4106,7 +4163,7 @@ public function setCustomerPrefix($customerPrefix) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerSuffix($customerSuffix) { @@ -4114,7 +4171,7 @@ public function setCustomerSuffix($customerSuffix) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerTaxvat($customerTaxvat) { @@ -4122,7 +4179,7 @@ public function setCustomerTaxvat($customerTaxvat) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountDescription($description) { @@ -4130,7 +4187,7 @@ public function setDiscountDescription($description) } /** - * {@inheritdoc} + * @inheritdoc */ public function setExtCustomerId($id) { @@ -4138,7 +4195,7 @@ public function setExtCustomerId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setExtOrderId($id) { @@ -4146,7 +4203,7 @@ public function setExtOrderId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setGlobalCurrencyCode($code) { @@ -4154,7 +4211,7 @@ public function setGlobalCurrencyCode($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function setHoldBeforeState($holdBeforeState) { @@ -4162,7 +4219,7 @@ public function setHoldBeforeState($holdBeforeState) } /** - * {@inheritdoc} + * @inheritdoc */ public function setHoldBeforeStatus($holdBeforeStatus) { @@ -4170,7 +4227,7 @@ public function setHoldBeforeStatus($holdBeforeStatus) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrderCurrencyCode($code) { @@ -4178,7 +4235,7 @@ public function setOrderCurrencyCode($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOriginalIncrementId($id) { @@ -4186,7 +4243,7 @@ public function setOriginalIncrementId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRelationChildId($id) { @@ -4194,7 +4251,7 @@ public function setRelationChildId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRelationChildRealId($realId) { @@ -4202,7 +4259,7 @@ public function setRelationChildRealId($realId) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRelationParentId($id) { @@ -4210,7 +4267,7 @@ public function setRelationParentId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRelationParentRealId($realId) { @@ -4218,7 +4275,7 @@ public function setRelationParentRealId($realId) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRemoteIp($remoteIp) { @@ -4226,7 +4283,7 @@ public function setRemoteIp($remoteIp) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreCurrencyCode($code) { @@ -4234,7 +4291,7 @@ public function setStoreCurrencyCode($code) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreName($storeName) { @@ -4242,7 +4299,7 @@ public function setStoreName($storeName) } /** - * {@inheritdoc} + * @inheritdoc */ public function setXForwardedFor($xForwardedFor) { @@ -4250,7 +4307,7 @@ public function setXForwardedFor($xForwardedFor) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerNote($customerNote) { @@ -4258,7 +4315,7 @@ public function setCustomerNote($customerNote) } /** - * {@inheritdoc} + * @inheritdoc */ public function setUpdatedAt($timestamp) { @@ -4266,7 +4323,7 @@ public function setUpdatedAt($timestamp) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalItemCount($totalItemCount) { @@ -4274,7 +4331,7 @@ public function setTotalItemCount($totalItemCount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerGender($customerGender) { @@ -4282,7 +4339,7 @@ public function setCustomerGender($customerGender) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationAmount($amount) { @@ -4290,7 +4347,7 @@ public function setDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationAmount($amount) { @@ -4298,7 +4355,7 @@ public function setBaseDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingDiscountTaxCompensationAmount($amount) { @@ -4306,7 +4363,7 @@ public function setShippingDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingDiscountTaxCompensationAmnt($amnt) { @@ -4314,7 +4371,7 @@ public function setBaseShippingDiscountTaxCompensationAmnt($amnt) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationInvoiced($discountTaxCompensationInvoiced) { @@ -4322,7 +4379,7 @@ public function setDiscountTaxCompensationInvoiced($discountTaxCompensationInvoi } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationInvoiced($baseDiscountTaxCompensationInvoiced) { @@ -4333,7 +4390,7 @@ public function setBaseDiscountTaxCompensationInvoiced($baseDiscountTaxCompensat } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationRefunded($discountTaxCompensationRefunded) { @@ -4344,7 +4401,7 @@ public function setDiscountTaxCompensationRefunded($discountTaxCompensationRefun } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationRefunded($baseDiscountTaxCompensationRefunded) { @@ -4355,7 +4412,7 @@ public function setBaseDiscountTaxCompensationRefunded($baseDiscountTaxCompensat } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingInclTax($amount) { @@ -4363,7 +4420,7 @@ public function setShippingInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingInclTax($amount) { diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender.php b/app/code/Magento/Sales/Model/Order/Email/Sender.php index 6d4480c4c45..564fd1e2a4b 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender.php @@ -65,6 +65,8 @@ public function __construct( } /** + * Send order email if it is enabled in configuration. + * * @param Order $order * @return bool */ @@ -81,17 +83,21 @@ protected function checkAndSend(Order $order) try { $sender->send(); - $sender->sendCopyTo(); } catch (\Exception $e) { $this->logger->error($e->getMessage()); - return false; } - + try { + $sender->sendCopyTo(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + } return true; } /** + * Populate order email template with customer information. + * * @param Order $order * @return void */ @@ -113,6 +119,8 @@ protected function prepareTemplate(Order $order) } /** + * Create Sender object using appropriate template and identity. + * * @return Sender */ protected function getSender() @@ -126,6 +134,8 @@ protected function getSender() } /** + * Get template options. + * * @return array */ protected function getTemplateOptions() @@ -137,6 +147,8 @@ protected function getTemplateOptions() } /** + * Render shipping address into html. + * * @param Order $order * @return string|null */ @@ -148,6 +160,8 @@ protected function getFormattedShippingAddress($order) } /** + * Render billing address into html. + * * @param Order $order * @return string|null */ diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index 401fdcd2b04..85e34f560bb 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -158,8 +158,7 @@ public function __construct( } /** - * Returns the total width in points of the string using the specified font and - * size. + * Returns the total width in points of the string using the specified font and size. * * This is not the most efficient way to perform this calculation. I'm * concentrating optimization efforts on the upcoming layout manager class. @@ -230,7 +229,7 @@ public function getAlignCenter($string, $x, $columnWidth, \Zend_Pdf_Resource_Fon * Insert logo to pdf page * * @param \Zend_Pdf_Page &$page - * @param null $store + * @param string|null $store * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -285,7 +284,7 @@ protected function insertLogo(&$page, $store = null) * Insert address to pdf page * * @param \Zend_Pdf_Page &$page - * @param null $store + * @param string|null $store * @return void */ protected function insertAddress(&$page, $store = null) @@ -641,11 +640,7 @@ protected function _sortTotalsList($a, $b) return 0; } - if ($a['sort_order'] == $b['sort_order']) { - return 0; - } - - return $a['sort_order'] > $b['sort_order'] ? 1 : -1; + return $a['sort_order'] <=> $b['sort_order']; } /** diff --git a/app/code/Magento/Sales/Model/Order/Status.php b/app/code/Magento/Sales/Model/Order/Status.php index 28895570542..4b33f4b4d92 100644 --- a/app/code/Magento/Sales/Model/Order/Status.php +++ b/app/code/Magento/Sales/Model/Order/Status.php @@ -12,6 +12,7 @@ * Class Status * * @method string getStatus() + * @method $this setStatus(string $status) * @method string getLabel() */ class Status extends \Magento\Sales\Model\AbstractModel diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php index f004a1ee37e..8758fc1da92 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php @@ -57,14 +57,16 @@ protected function _construct() } /** - * Used to emulate after load functionality for each item without loading them + * Unserialize packages in each item * * @return $this */ protected function _afterLoad() { - $this->walk('afterLoad'); + foreach ($this->_items as $item) { + $this->getResource()->unserializeFields($item); + } - return $this; + return parent::_afterLoad(); } } diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php index e8f2e6e5305..bac72e0b522 100644 --- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php +++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php @@ -166,8 +166,8 @@ public function refund( $creditmemo->getOrder(), !$offlineRequested ); - $this->getOrderRepository()->save($order); $this->creditmemoRepository->save($creditmemo); + $this->getOrderRepository()->save($order); $connection->commit(); } catch (\Exception $e) { $connection->rollBack(); @@ -178,6 +178,8 @@ public function refund( } /** + * Validates if credit memo is available for refund. + * * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo * @return bool * @throws \Magento\Framework\Exception\LocalizedException @@ -208,8 +210,9 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface } /** - * @return \Magento\Sales\Model\Order\RefundAdapterInterface + * Gets the instance of RefundAdapterInterface * + * @return \Magento\Sales\Model\Order\RefundAdapterInterface * @deprecated 100.1.3 */ private function getRefundAdapter() @@ -222,8 +225,9 @@ private function getRefundAdapter() } /** - * @return \Magento\Framework\App\ResourceConnection|mixed + * Gets instance of ResourceConnection. * + * @return \Magento\Framework\App\ResourceConnection|mixed * @deprecated 100.1.3 */ private function getResource() @@ -236,8 +240,9 @@ private function getResource() } /** - * @return \Magento\Sales\Api\OrderRepositoryInterface + * Gets instance of OrderRepositoryInterface. * + * @return \Magento\Sales\Api\OrderRepositoryInterface * @deprecated 100.1.3 */ private function getOrderRepository() @@ -250,8 +255,9 @@ private function getOrderRepository() } /** - * @return \Magento\Sales\Api\InvoiceRepositoryInterface + * Gets instance of InvoiceRepositoryInterface * + * @return \Magento\Sales\Api\InvoiceRepositoryInterface * @deprecated 100.1.3 */ private function getInvoiceRepository() diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml new file mode 100644 index 00000000000..fe2fc49f1d0 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml new file mode 100644 index 00000000000..4e89e5476c3 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontSalesOrderPrintPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontSalesOrderPrintPage.xml new file mode 100644 index 00000000000..874e6889ec5 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontSalesOrderPrintPage.xml @@ -0,0 +1,15 @@ + + + + + +
+ + diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml index 8fb45295bd2..78b9c853ff6 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml index e4d329bc850..4350ffeb033 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml @@ -13,5 +13,6 @@ +
\ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml new file mode 100644 index 00000000000..b08a66140fa --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index 05f20371851..82f5b515eb9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml @@ -78,8 +78,10 @@ + + diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml new file mode 100644 index 00000000000..5f6ea0937b5 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + <description value="Admin should not be able place order with Free Shipping method if Minimum Order Amount does not match Order total"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-61001"/> + <group value="sales"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">100</field> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="DisableFlatRateShippingMethodConfig" stepKey="disableFlatRate"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotal" stepKey="setFreeShippingSubtotal"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRate"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotalToDefault" stepKey="setFreeShippingSubtotalToDefault"/> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="cache:flush" stepKey="flushCache2"/> + </after> + <!--Create new order with existing customer--> + <actionGroup ref="navigateToNewOrderPageExistingCustomer" stepKey="goToCreateOrderPage"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <!--Add product to order--> + <actionGroup ref="addSimpleProductToOrder" stepKey="addProductToOrder"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <click selector="{{AdminOrderFormPaymentSection.header}}" stepKey="unfocus"/> + <waitForPageLoad stepKey="waitForJavascriptToFinish"/> + <!--Click *Get shipping methods and rates* and see that Free Shipping is absent--> + <click selector="{{AdminOrderFormPaymentSection.getShippingMethods}}" stepKey="clickGetShippingMehods"/> + <dontSeeElement selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="seeAbsentFreeShipping"/> + <!--Submit Order and verify that Order isn't placed--> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + + <dontSeeElement selector="{{AdminMessagesSection.successMessage}}" stepKey="seeSuccessMessage"/> + <seeElement selector="{{AdminMessagesSection.errorMessage}}" stepKey="seeErrorMessage"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml new file mode 100644 index 00000000000..ec725ad5fe4 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontRedirectToOrderHistory"> + <annotations> + <features value="Redirection Rules"/> + <title value="Create Invoice"/> + <description + value="Check while order printing URL with an id of not relevant order redirects to order history"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-92854"/> + <group value="sales"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer2"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCustomer2" stepKey="deleteCustomer2"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!--Create an order at Storefront as Customer 1 --> + <actionGroup ref="CreateOrderToPrintPageActionGroup" stepKey="createOrderToPrint"> + <argument name="Category" value="$$createCategory$$"/> + </actionGroup> + + <!--Go to 'print order' page by grabbed order id--> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderIdFromURL"/> + <switchToNextTab stepKey="switchToPrintPage"/> + <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkPrintPage"/> + <openNewTab stepKey="openNewTab"/> + <switchToNextTab stepKey="switchForward"/> + <amOnPage url="{{StorefrontSalesOrderPrintPage.url({$grabOrderIdFromURL})}}" stepKey="duplicatePrintPage"/> + + <!--Log out as customer 1--> + <switchToNextTab stepKey="switchForward2"/> + <openNewTab stepKey="openNewTab2"/> + <amOnPage url="{{StorefrontCustomerSignOutPage.url}}" stepKey="signOut"/> + <waitForLoadingMaskToDisappear stepKey="waitSignOutPage"/> + + <!--Log in to Storefront as Customer 2 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp2"> + <argument name="Customer" value="$$createCustomer2$$"/> + </actionGroup> + + <!--Create an order at Storefront as Customer 2 --> + <actionGroup ref="CreateOrderToPrintPageActionGroup" stepKey="createOrderToPrint2"> + <argument name="Category" value="$$createCategory$$"/> + </actionGroup> + + <!--Try to load 'print order' page with not relevant order id to be redirected to 'order history' page--> + <switchToNextTab stepKey="switchToPrintPage2"/> + <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkPrintPage2"/> + <openNewTab stepKey="openNewTab3"/> + <switchToNextTab stepKey="switchForward4"/> + <amOnPage url="{{StorefrontSalesOrderPrintPage.url({$grabOrderIdFromURL})}}" stepKey="duplicatePrintPage2"/> + <seeElement selector="{{StorefrontCustomerOrderSection.isMyOrdersSection}}" stepKey="waitOrderHistoryPage"/> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultMergerTest.php b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultMergerTest.php new file mode 100644 index 00000000000..4236890a2a3 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultMergerTest.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Model\ValidatorResultInterface; +use Magento\Sales\Model\ValidatorResultInterfaceFactory; +use Magento\Sales\Model\ValidatorResultMerger; + +/** + * @covers \Magento\Sales\Model\ValidatorResultMerger + */ +class ValidatorResultMergerTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var ValidatorResultMerger + */ + private $validatorResultMerger; + + /** + * Object Manager + * + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ValidatorResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $validatorResultFactoryMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->validatorResultFactoryMock = $this->getMockBuilder(ValidatorResultInterfaceFactory::class) + ->setMethods(['create'])->disableOriginalConstructor()->getMock(); + $this->objectManager = new ObjectManager($this); + $this->validatorResultMerger = $this->objectManager->getObject( + ValidatorResultMerger::class, + [ + 'validatorResultInterfaceFactory' => $this->validatorResultFactoryMock, + ] + ); + } + + /** + * Test merge method + * + * @return void + */ + public function testMerge() + { + $validatorResultMock = $this->createMock(ValidatorResultInterface::class); + $orderValidationResultMock = $this->createMock(ValidatorResultInterface::class); + $creditmemoValidationResultMock = $this->createMock(ValidatorResultInterface::class); + $itemsValidationMessages = [['test04', 'test05'], ['test06']]; + $this->validatorResultFactoryMock->expects($this->once())->method('create') + ->willReturn($validatorResultMock); + $orderValidationResultMock->expects($this->once())->method('getMessages')->willReturn(['test01', 'test02']); + $creditmemoValidationResultMock->expects($this->once())->method('getMessages')->willReturn(['test03']); + + $validatorResultMock->expects($this->at(0))->method('addMessage')->with('test01'); + $validatorResultMock->expects($this->at(1))->method('addMessage')->with('test02'); + $validatorResultMock->expects($this->at(2))->method('addMessage')->with('test03'); + $validatorResultMock->expects($this->at(3))->method('addMessage')->with('test04'); + $validatorResultMock->expects($this->at(4))->method('addMessage')->with('test05'); + $validatorResultMock->expects($this->at(5))->method('addMessage')->with('test06'); + $expected = $validatorResultMock; + $actual = $this->validatorResultMerger->merge( + $orderValidationResultMock, + $creditmemoValidationResultMock, + ...$itemsValidationMessages + ); + $this->assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultTest.php b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultTest.php new file mode 100644 index 00000000000..f4ab2d4f48e --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultTest.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Model\ValidatorResult; + +/** + * @covers \Magento\Sales\Model\ValidatorResult + */ +class ValidatorResultTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var ValidatorResult + */ + private $validatorResult; + + /** + * Object Manager + * + * @var ObjectManager + */ + private $objectManager; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->validatorResult = $this->objectManager->getObject(ValidatorResult::class); + } + + /** + * Test addMessage method + * + * @return void + */ + public function testAddMessages() + { + $messageFirst = 'Sample message 01.'; + $messageSecond = 'Sample messages 02.'; + $messageThird = 'Sample messages 03.'; + $expected = [$messageFirst, $messageSecond, $messageThird]; + $this->validatorResult->addMessage($messageFirst); + $this->validatorResult->addMessage($messageSecond); + $this->validatorResult->addMessage($messageThird); + $actual = $this->validatorResult->getMessages(); + $this->assertEquals($expected, $actual); + } + + /** + * Test hasMessages method + * + * @return void + */ + public function testHasMessages() + { + $this->assertFalse($this->validatorResult->hasMessages()); + $messageFirst = 'Sample message 01.'; + $messageSecond = 'Sample messages 02.'; + $this->validatorResult->addMessage($messageFirst); + $this->validatorResult->addMessage($messageSecond); + $this->assertTrue($this->validatorResult->hasMessages()); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php new file mode 100644 index 00000000000..c6e02151b9b --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Observer; + +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Observer\AssignOrderToCustomerObserver; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * Class AssignOrderToCustomerObserverTest + */ +class AssignOrderToCustomerObserverTest extends TestCase +{ + /** @var AssignOrderToCustomerObserver */ + protected $sut; + + /** @var OrderRepositoryInterface|PHPUnit_Framework_MockObject_MockObject */ + protected $orderRepositoryMock; + + /** + * Set Up + */ + protected function setUp() + { + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->sut = new AssignOrderToCustomerObserver($this->orderRepositoryMock); + } + + /** + * Test assigning order to customer after issuing guest order + * + * @dataProvider getCustomerIds + * @param null|int $customerId + * @return void + */ + public function testAssignOrderToCustomerAfterGuestOrder($customerId) + { + $orderId = 1; + /** @var Observer|PHPUnit_Framework_MockObject_MockObject $observerMock */ + $observerMock = $this->createMock(Observer::class); + /** @var Event|PHPUnit_Framework_MockObject_MockObject $eventMock */ + $eventMock = $this->getMockBuilder(Event::class)->disableOriginalConstructor() + ->setMethods(['getData']) + ->getMock(); + /** @var CustomerInterface|PHPUnit_Framework_MockObject_MockObject $customerMock */ + $customerMock = $this->createMock(CustomerInterface::class); + /** @var OrderInterface|PHPUnit_Framework_MockObject_MockObject $orderMock */ + $orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->any())->method('getData') + ->willReturnMap([ + ['delegate_data', null, ['__sales_assign_order_id' => $orderId]], + ['customer_data_object', null, $customerMock] + ]); + $orderMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->orderRepositoryMock->expects($this->once())->method('get')->with($orderId) + ->willReturn($orderMock); + if (!$customerId) { + $this->orderRepositoryMock->expects($this->once())->method('save')->with($orderMock); + $this->sut->execute($observerMock); + return ; + } + + $this->orderRepositoryMock->expects($this->never())->method('save')->with($orderMock); + $this->sut->execute($observerMock); + } + + /** + * Customer id assigned to order + * + * @return array + */ + public function getCustomerIds() + { + return [[null, 1]]; + } +} diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml index 4b716e76109..fda6e75cf00 100644 --- a/app/code/Magento/Sales/etc/db_schema.xml +++ b/app/code/Magento/Sales/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="sales_order" resource="sales" engine="innodb" comment="Sales Flat Order"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="state" nullable="true" length="32" comment="State"/> <column xsi:type="varchar" name="status" nullable="true" length="32" comment="Status"/> <column xsi:type="varchar" name="coupon_code" nullable="true" length="255" comment="Coupon Code"/> @@ -297,13 +297,13 @@ </table> <table name="sales_order_grid" resource="sales" engine="innodb" comment="Sales Flat Order Grid"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="status" nullable="true" length="32" comment="Status"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="varchar" name="store_name" nullable="true" length="255" comment="Store Name"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" - comment="Customer Id"/> + comment="Customer ID"/> <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Grand Total"/> <column xsi:type="decimal" name="base_total_paid" scale="4" precision="12" unsigned="false" nullable="true" @@ -386,17 +386,17 @@ </table> <table name="sales_order_address" resource="sales" engine="innodb" comment="Sales Flat Order Address"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="true" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="int" name="customer_address_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Customer Address Id"/> + comment="Customer Address ID"/> <column xsi:type="int" name="quote_address_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Quote Address Id"/> + comment="Quote Address ID"/> <column xsi:type="int" name="region_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Region Id"/> + comment="Region ID"/> <column xsi:type="int" name="customer_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Customer Id"/> + comment="Customer ID"/> <column xsi:type="varchar" name="fax" nullable="true" length="255" comment="Fax"/> <column xsi:type="varchar" name="region" nullable="true" length="255" comment="Region"/> <column xsi:type="varchar" name="postcode" nullable="true" length="255" comment="Postcode"/> @@ -431,9 +431,9 @@ </table> <table name="sales_order_status_history" resource="sales" engine="innodb" comment="Sales Flat Order Status History"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="int" name="is_customer_notified" padding="11" unsigned="false" nullable="true" identity="false" comment="Is Customer Notified"/> <column xsi:type="smallint" name="is_visible_on_front" padding="5" unsigned="true" nullable="false" @@ -602,9 +602,9 @@ </table> <table name="sales_order_payment" resource="sales" engine="innodb" comment="Sales Flat Order Payment"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="decimal" name="base_shipping_captured" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Shipping Captured"/> <column xsi:type="decimal" name="shipping_captured" scale="4" precision="12" unsigned="false" nullable="true" @@ -696,9 +696,9 @@ </table> <table name="sales_shipment" resource="sales" engine="innodb" comment="Sales Flat Shipment"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="decimal" name="total_weight" scale="4" precision="12" unsigned="false" nullable="true" comment="Total Weight"/> <column xsi:type="decimal" name="total_qty" scale="4" precision="12" unsigned="false" nullable="true" @@ -708,13 +708,13 @@ <column xsi:type="smallint" name="send_email" padding="5" unsigned="true" nullable="true" identity="false" comment="Send Email"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Order Id"/> + comment="Order ID"/> <column xsi:type="int" name="customer_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Customer Id"/> + comment="Customer ID"/> <column xsi:type="int" name="shipping_address_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Shipping Address Id"/> + comment="Shipping Address ID"/> <column xsi:type="int" name="billing_address_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Billing Address Id"/> + comment="Billing Address ID"/> <column xsi:type="int" name="shipment_status" padding="11" unsigned="false" nullable="true" identity="false" comment="Shipment Status"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> @@ -762,13 +762,13 @@ </table> <table name="sales_shipment_grid" resource="sales" engine="innodb" comment="Sales Flat Shipment Grid"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="varchar" name="order_increment_id" nullable="false" length="32" comment="Order Increment Id"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Order Id"/> + comment="Order ID"/> <column xsi:type="timestamp" name="order_created_at" on_update="true" nullable="true" default="CURRENT_TIMESTAMP" comment="Order Increment Id"/> <column xsi:type="varchar" name="customer_name" nullable="false" length="128" comment="Customer Name"/> @@ -837,9 +837,9 @@ </table> <table name="sales_shipment_item" resource="sales" engine="innodb" comment="Sales Flat Shipment Item"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="true" comment="Row Total"/> <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" @@ -848,9 +848,9 @@ comment="Weight"/> <column xsi:type="decimal" name="qty" scale="4" precision="12" unsigned="false" nullable="true" comment="Qty"/> <column xsi:type="int" name="product_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Product Id"/> + comment="Product ID"/> <column xsi:type="int" name="order_item_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Order Item Id"/> + comment="Order Item ID"/> <column xsi:type="text" name="additional_data" nullable="true" comment="Additional Data"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Name"/> @@ -867,14 +867,14 @@ </table> <table name="sales_shipment_track" resource="sales" engine="innodb" comment="Sales Flat Shipment Track"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="true" comment="Weight"/> <column xsi:type="decimal" name="qty" scale="4" precision="12" unsigned="false" nullable="true" comment="Qty"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Order Id"/> + comment="Order ID"/> <column xsi:type="text" name="track_number" nullable="true" comment="Number"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="varchar" name="title" nullable="true" length="255" comment="Title"/> @@ -901,9 +901,9 @@ </table> <table name="sales_shipment_comment" resource="sales" engine="innodb" comment="Sales Flat Shipment Comment"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="int" name="is_customer_notified" padding="11" unsigned="false" nullable="true" identity="false" comment="Is Customer Notified"/> <column xsi:type="smallint" name="is_visible_on_front" padding="5" unsigned="true" nullable="false" @@ -926,9 +926,9 @@ </table> <table name="sales_invoice" resource="sales" engine="innodb" comment="Sales Flat Invoice"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Grand Total"/> <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" @@ -1051,15 +1051,15 @@ </table> <table name="sales_invoice_grid" resource="sales" engine="innodb" comment="Sales Flat Invoice Grid"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="int" name="state" padding="11" unsigned="false" nullable="true" identity="false" comment="State"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="varchar" name="store_name" nullable="true" length="255" comment="Store Name"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Order Id"/> + comment="Order ID"/> <column xsi:type="varchar" name="order_increment_id" nullable="true" length="50" comment="Order Increment Id"/> <column xsi:type="timestamp" name="order_created_at" on_update="false" nullable="true" comment="Order Created At"/> @@ -1136,9 +1136,9 @@ </table> <table name="sales_invoice_item" resource="sales" engine="innodb" comment="Sales Flat Invoice Item"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="decimal" name="base_price" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Price"/> <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" @@ -1192,9 +1192,9 @@ </table> <table name="sales_invoice_comment" resource="sales" engine="innodb" comment="Sales Flat Invoice Comment"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="smallint" name="is_customer_notified" padding="5" unsigned="true" nullable="true" identity="false" comment="Is Customer Notified"/> <column xsi:type="smallint" name="is_visible_on_front" padding="5" unsigned="true" nullable="false" @@ -1217,9 +1217,9 @@ </table> <table name="sales_creditmemo" resource="sales" engine="innodb" comment="Sales Flat Creditmemo"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" comment="Adjustment Positive"/> <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" @@ -1350,12 +1350,12 @@ </table> <table name="sales_creditmemo_grid" resource="sales" engine="innodb" comment="Sales Flat Creditmemo Grid"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="true" comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true" comment="Updated At"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Order Id"/> + comment="Order ID"/> <column xsi:type="varchar" name="order_increment_id" nullable="true" length="50" comment="Order Increment Id"/> <column xsi:type="timestamp" name="order_created_at" on_update="false" nullable="true" comment="Order Created At"/> @@ -1366,7 +1366,7 @@ comment="Base Grand Total"/> <column xsi:type="varchar" name="order_status" nullable="true" length="32" comment="Order Status"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" - comment="Store Id"/> + comment="Store ID"/> <column xsi:type="varchar" name="billing_address" nullable="true" length="255" comment="Billing Address"/> <column xsi:type="varchar" name="shipping_address" nullable="true" length="255" comment="Shipping Address"/> <column xsi:type="varchar" name="customer_name" nullable="false" length="128" comment="Customer Name"/> @@ -1438,9 +1438,9 @@ </table> <table name="sales_creditmemo_item" resource="sales" engine="innodb" comment="Sales Flat Creditmemo Item"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="decimal" name="base_price" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Price"/> <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" @@ -1469,9 +1469,9 @@ <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> <column xsi:type="int" name="product_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Product Id"/> + comment="Product ID"/> <column xsi:type="int" name="order_item_id" padding="11" unsigned="false" nullable="true" identity="false" - comment="Order Item Id"/> + comment="Order Item ID"/> <column xsi:type="text" name="additional_data" nullable="true" comment="Additional Data"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="varchar" name="sku" nullable="true" length="255" comment="Sku"/> @@ -1494,9 +1494,9 @@ </table> <table name="sales_creditmemo_comment" resource="sales" engine="innodb" comment="Sales Flat Creditmemo Comment"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Parent Id"/> + comment="Parent ID"/> <column xsi:type="int" name="is_customer_notified" padding="11" unsigned="false" nullable="true" identity="false" comment="Is Customer Notified"/> <column xsi:type="smallint" name="is_visible_on_front" padding="5" unsigned="true" nullable="false" diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js index b61f4617e1b..259ca2165e6 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js @@ -48,7 +48,7 @@ define([ if (typeof controlButtonArea != 'undefined') { var buttons = controlButtonArea.childElements(); for (var i = 0; i < buttons.length; i++) { - if (buttons[i].innerHTML.include(button.label)) { + if (buttons[i].innerHTML.include(button.getLabel())) { return; } } @@ -1171,8 +1171,12 @@ define([ submit : function() { - jQuery('#edit_form').trigger('processStart'); - jQuery('#edit_form').trigger('submitOrder'); + var $editForm = jQuery('#edit_form'); + + if ($editForm.valid()) { + $editForm.trigger('processStart'); + $editForm.trigger('submitOrder'); + } }, _realSubmit: function () { @@ -1432,6 +1436,10 @@ define([ node.update('<span>' + this._label + '</span>'); content[position] = node; Element.insert(element, content); + }, + + getLabel: function(){ + return this._label; } }; diff --git a/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml b/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml index 0e2689e3e21..99af16ce629 100644 --- a/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml +++ b/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml @@ -12,11 +12,20 @@ <body> <attribute name="class" value="sales-guest-view"/> <referenceContainer name="page.main.title"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.status" template="Magento_Sales::order/order_status.phtml" /> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.date" template="Magento_Sales::order/order_date.phtml" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.status" + template="Magento_Sales::order/order_status.phtml" + cacheable="false" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.date" + template="Magento_Sales::order/order_date.phtml" + cacheable="false" /> </referenceContainer> <referenceContainer name="content"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="sales.order.print" template="Magento_Sales::order/view.phtml"> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="sales.order.print" + template="Magento_Sales::order/view.phtml" + cacheable="false"> <block class="Magento\Sales\Block\Order\PrintShipment" name="order_items" template="Magento_Sales::order/items.phtml"> <block class="Magento\Framework\View\Element\RendererList" name="sales.order.print.renderers" as="renderer.list" /> <block class="Magento\Sales\Block\Order\Totals" name="order_totals" template="Magento_Sales::order/totals.phtml"> diff --git a/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml b/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml index 50ffe979651..4410a6fc4a9 100644 --- a/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml +++ b/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml @@ -12,12 +12,21 @@ <body> <attribute name="class" value="account"/> <referenceContainer name="page.main.title"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.status" template="Magento_Sales::order/order_status.phtml" /> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.date" template="Magento_Sales::order/order_date.phtml" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.status" + template="Magento_Sales::order/order_status.phtml" + cacheable="false" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.date" + template="Magento_Sales::order/order_date.phtml" + cacheable="false" /> </referenceContainer> <referenceContainer name="content"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="sales.order.print" template="Magento_Sales::order/view.phtml"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order_items" template="Magento_Sales::order/items.phtml"> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="sales.order.print" + template="Magento_Sales::order/view.phtml" + cacheable="false"> + <block class="Magento\Sales\Block\Order\Items" name="order_items" template="Magento_Sales::order/items.phtml"> <block class="Magento\Framework\View\Element\RendererList" name="sales.order.print.renderers" as="renderer.list" /> <block class="Magento\Sales\Block\Order\Totals" name="order_totals" template="Magento_Sales::order/totals.phtml"> <arguments> diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/DeleteButton.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/DeleteButton.php index 0cb286056d8..bee7573c1fe 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/DeleteButton.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/DeleteButton.php @@ -26,7 +26,7 @@ public function getButtonData() 'class' => 'delete', 'on_click' => 'deleteConfirm(\'' . __( 'Are you sure you want to delete this?' - ) . '\', \'' . $this->urlBuilder->getUrl('*/*/delete', ['id' => $ruleId]) . '\')', + ) . '\', \'' . $this->urlBuilder->getUrl('*/*/delete', ['id' => $ruleId]) . '\', {data: {}})', 'sort_order' => 20, ]; } diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php index 28993e15949..45b717fb9bd 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -class Delete extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpPostActionInterface { /** * Delete promo quote action diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php index 717c71fde48..3255ee0f2b7 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -class Edit extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpGetActionInterface { /** * @var \Magento\Framework\View\Result\PageFactory diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Index.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Index.php index 0c11ee3785a..4f3c712049e 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Index.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -class Index extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpGetActionInterface { /** * Index action diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewAction.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewAction.php index ecf39605a87..5adef0552bc 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewAction.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewAction.php @@ -6,7 +6,9 @@ */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -class NewAction extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class NewAction extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpGetActionInterface { /** * New promo quote action diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml index 55b2e9c10fd..37e171823b1 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml @@ -9,20 +9,16 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="ApplyCartRuleOnStorefrontActionGroup"> <arguments> - <argument name="Product" defaultValue="_defaultProduct"/> - <argument name="Coupon" defaultValue="SimpleSalesRuleCoupon"/> + <argument name="product"/> + <argument name="couponCode" type="string"/> </arguments> - <amOnPage url="{{Product.name}}.html" stepKey="navigateToProductPage"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart" /> - <waitForText userInput="You added {{Product.name}} to your shopping cart." stepKey="waitForAddedBtn"/> - <amOnPage url="/checkout/cart/" stepKey="onPageShoppingCart"/> - <waitForPageLoad stepKey="waitForPageLoad2"/> - <click selector="{{DiscountSection.DiscountTab}}" stepKey="scrollToDiscountTab" /> - <fillField selector="{{DiscountSection.CouponInput}}" userInput="{{Coupon.code}}" stepKey="fillCouponCode" /> + <waitForText userInput="You added {{product.name}} to your shopping cart." stepKey="waitForText"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{DiscountSection.DiscountTab}}" stepKey="clickToDiscountTab" /> + <fillField selector="{{DiscountSection.CouponInput}}" userInput="{{couponCode}}" stepKey="fillCouponCode"/> <click selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="applyCode"/> - <waitForPageLoad stepKey="waitForPageLoad3"/> - <waitForText userInput="You used coupon code" stepKey="waitForText"/> - <see userInput="You used coupon code" stepKey="assertText"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index 6ebc78a6771..dbca35d6432 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -60,4 +60,32 @@ <data key="apply">Percent of product price discount</data> <data key="discountAmount">50</data> </entity> + <entity name="SalesRuleSpecificCoupon" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="description">Sales Rule Descritpion</data> + <array key="website_ids"> + <item>1</item> + </array> + <array key="customer_group_ids"> + <item>0</item> + <item>1</item> + <item>3</item> + </array> + <data key="uses_per_customer">1</data> + <data key="is_active">true</data> + <data key="stop_rules_processing">false</data> + <data key="is_advanced">true</data> + <data key="sort_order">2</data> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + <data key="discount_qty">1</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">false</data> + <data key="times_used">1</data> + <data key="is_rss">false</data> + <data key="coupon_type">SPECIFIC_COUPON</data> + <data key="use_auto_generation">true</data> + <data key="uses_per_coupon">2</data> + <data key="simple_free_shipping">1</data> + </entity> </entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml index cbab097c529..e14bfb554b3 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml @@ -8,8 +8,9 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="DiscountSection"> - <element name="DiscountTab" type="button" selector="//strong[text()='Apply Discount Code']"/> + <element name="DiscountTab" type="button" selector="//*[text()='Apply Discount Code']"/> <element name="CouponInput" type="input" selector="#coupon_code"/> + <element name="DiscountInput" type="input" selector="#discount-code"/> <element name="ApplyCodeBtn" type="button" selector="//span[text()='Apply Discount']"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml new file mode 100644 index 00000000000..6f9474278bf --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAutoGeneratedCouponCodeTest"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="[Cart Price Rule] Auto generated coupon code considers 'Uses per Coupon' and 'Uses per Customer' options"/> + <description + value="[Cart Price Rule] Auto generated coupon code considers 'Uses per Coupon' and 'Uses per Customer' options"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-59323"/> + <group value="salesRule"/> + </annotations> + + <before> + <!-- Create customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <!-- Create simple product--> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <!-- Create a cart price rule --> + <createData entity="SalesRuleSpecificCoupon" stepKey="createSalesRule"/> + </before> + + <after> + <!-- Delete the cart price rule we made during the test --> + <deleteData createDataKey="createSalesRule" stepKey="deleteSalesRule"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Search Cart Price Rule and go to edit Cart Price Rule --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <fillField selector="{{AdminCartPriceRulesSection.filterByNameInput}}" userInput="$$createSalesRule.name$$" + stepKey="fillFieldFilterByName"/> + <click selector="{{AdminCartPriceRulesSection.searchButton}}" stepKey="clickSearchButton"/> + <see selector="{{AdminCartPriceRulesSection.nameColumns}}" userInput="$$createSalesRule.name$$" + stepKey="seeRuleName"/> + <click selector="{{AdminCartPriceRulesSection.rowContainingText($$createSalesRule.name$$)}}" + stepKey="goToEditRule"/> + + <!-- Step 3-4. Navigate to Manage Coupon Codes section to generate 1 coupon code --> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.manageCouponCodesHeader}}" + dependentSelector="{{AdminCartPriceRulesFormSection.manageCouponCodesHeader}}" visible="true" + stepKey="clickManageCouponCodes"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponQty}}" userInput="1" stepKey="fillFieldCouponQty"/> + <click selector="{{AdminCartPriceRulesFormSection.generateCouponsButton}}" stepKey="clickGenerateCoupon"/> + <see selector="{{AdminCartPriceRulesFormSection.successMessage}}" userInput="1 coupon(s) have been generated." + stepKey="seeSuccessMessage"/> + <grabTextFrom selector="{{AdminCartPriceRulesFormSection.generatedCouponByIndex('1')}}" + stepKey="couponCode"/> + + <!-- Step: 5. Login to storefront as previously created customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Step: 6-7. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='You used coupon code "{$couponCode}"' stepKey="waitForText"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput='You used coupon code "{$couponCode}"' + stepKey="seeSuccessMessage1"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementDiscountVisible"/> + + <!-- Step 8. Go to Checkout and Click Place Order button --> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" + stepKey="selectFlatShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" + stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage"/> + + <!-- Step: 9-10. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage1"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule1"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='The coupon code "{$couponCode}" is not valid.' stepKey="waitForText1"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='The coupon code "{$couponCode}" is not valid.' + stepKey="seeErrorMessages"/> + <waitForElementNotVisible selector="{{CheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementNotDiscountVisible"/> + + <!-- Step 11. Log out from storefront --> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="storefrontSignOut"/> + + <!-- Step: 12-13. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage2"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule2"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='You used coupon code "{$couponCode}"' stepKey="waitForText2"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput='You used coupon code "{$couponCode}"' + stepKey="seeSuccessMessage2"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementDiscountVisible1"/> + + <!-- Step 14. Go to Checkout and Click Place Order button --> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout1"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customerVar" value="CustomerEntityOne"/> + <argument name="customerAddressVar" value="CustomerAddressSimple"/> + </actionGroup> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/> + <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage1"/> + + <!-- Step; 15-16. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage3"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule3"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='The coupon code "{$couponCode}" is not valid.' stepKey="waitForText3"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='The coupon code "{$couponCode}" is not valid.' + stepKey="seeErrorMessages1"/> + <waitForElementNotVisible selector="{{CheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementNotDiscountVisible1"/> + + <!-- Step: 17. Reset Cookie --> + <resetCookie userInput="PHPSESSID" stepKey="resetCookie"/> + + <!-- Step: 18-19. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage4"/> + <waitForPageLoad stepKey="waitForPageLoad6"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule4"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='The coupon code "{$couponCode}" is not valid.' stepKey="waitForText4"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='The coupon code "{$couponCode}" is not valid.' + stepKey="seeErrorMessages2"/> + <waitForElementNotVisible selector="{{CheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementNotDiscountVisible2"/> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php b/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php index fb01476ed6b..7812444c686 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php @@ -25,13 +25,16 @@ class DeleteButtonTest extends \PHPUnit\Framework\TestCase */ protected $registryMock; + /** + * @inheritDoc + */ protected function setUp() { $this->urlBuilderMock = $this->createMock(\Magento\Framework\UrlInterface::class); $this->registryMock = $this->createMock(\Magento\Framework\Registry::class); $contextMock = $this->createMock(\Magento\Backend\Block\Widget\Context::class); - $contextMock->expects($this->once())->method('getUrlBuilder')->willReturn($this->urlBuilderMock); + $contextMock->expects($this->any())->method('getUrlBuilder')->willReturn($this->urlBuilderMock); $this->model = (new ObjectManager($this))->getObject( \Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\DeleteButton::class, @@ -42,33 +45,9 @@ protected function setUp() ); } - public function testGetButtonData() - { - $ruleId = 42; - $deleteUrl = 'http://magento.com/rule/delete/' . $ruleId; - $ruleMock = new \Magento\Framework\DataObject(['id' => $ruleId]); - - $this->registryMock->expects($this->once()) - ->method('registry') - ->with(RegistryConstants::CURRENT_SALES_RULE) - ->willReturn($ruleMock); - $this->urlBuilderMock->expects($this->once()) - ->method('getUrl') - ->with('*/*/delete', ['id' => $ruleId]) - ->willReturn($deleteUrl); - - $data = [ - 'label' => __('Delete'), - 'class' => 'delete', - 'on_click' => 'deleteConfirm(\'' . __( - 'Are you sure you want to delete this?' - ) . '\', \'' . $deleteUrl . '\')', - 'sort_order' => 20, - ]; - - $this->assertEquals($data, $this->model->getButtonData()); - } - + /** + * Test empty response without a present rule. + */ public function testGetButtonDataWithoutRule() { $this->assertEquals([], $this->model->getButtonData()); diff --git a/app/code/Magento/SalesRule/etc/db_schema.xml b/app/code/Magento/SalesRule/etc/db_schema.xml index 3d882ee2eae..145ed0c96d9 100644 --- a/app/code/Magento/SalesRule/etc/db_schema.xml +++ b/app/code/Magento/SalesRule/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="salesrule" resource="default" engine="innodb" comment="Salesrule"> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Name"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="date" name="from_date" comment="From"/> @@ -60,7 +60,7 @@ <column xsi:type="int" name="coupon_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Coupon Id"/> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="varchar" name="code" nullable="true" length="255" comment="Code"/> <column xsi:type="int" name="usage_limit" padding="10" unsigned="true" nullable="true" identity="false" comment="Usage Limit"/> @@ -117,7 +117,7 @@ <column xsi:type="int" name="rule_customer_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Rule Customer Id"/> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Customer Id"/> <column xsi:type="smallint" name="times_used" padding="5" unsigned="true" nullable="false" identity="false" @@ -143,7 +143,7 @@ <column xsi:type="int" name="label_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Label Id"/> <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" - comment="Rule Id"/> + comment="Rule ID"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Store Id"/> <column xsi:type="varchar" name="label" nullable="true" length="255" comment="Label"/> @@ -163,7 +163,8 @@ </index> </table> <table name="salesrule_product_attribute" resource="default" engine="innodb" comment="Salesrule Product Attribute"> - <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false"/> + <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Rule ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website Id"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" @@ -318,7 +319,8 @@ </index> </table> <table name="salesrule_website" resource="default" engine="innodb" comment="Sales Rules To Websites Relations"> - <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false"/> + <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Rule ID"/> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website Id"/> <constraint xsi:type="primary" name="PRIMARY"> @@ -336,7 +338,8 @@ </table> <table name="salesrule_customer_group" resource="default" engine="innodb" comment="Sales Rules To Customer Groups Relations"> - <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false"/> + <column xsi:type="int" name="rule_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Rule ID"/> <column xsi:type="int" name="customer_group_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Customer Group Id"/> <constraint xsi:type="primary" name="PRIMARY"> diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php index c7adf32da0f..ce3dfcc601f 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php @@ -5,10 +5,11 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Search\Controller\Adminhtml\Term as TermController; use Magento\Framework\Controller\ResultFactory; -class Delete extends TermController +class Delete extends TermController implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php index 3ee0ea24037..cd1ef7553c7 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php @@ -5,12 +5,13 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Search\Controller\Adminhtml\Term as TermController; use Magento\Backend\App\Action\Context; use Magento\Framework\Registry; use Magento\Framework\Controller\ResultFactory; -class Edit extends TermController +class Edit extends TermController implements HttpGetActionInterface { /** * Core registry diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Index.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Index.php index f7d0b865907..c097cc08489 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Index.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Index.php @@ -5,9 +5,10 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Search\Controller\Adminhtml\Term as TermController; -class Index extends TermController +class Index extends TermController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php b/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php index f6874078f2f..84e475b7275 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php @@ -5,10 +5,11 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Search\Controller\Adminhtml\Term as TermController; use Magento\Framework\Controller\ResultFactory; -class MassDelete extends TermController +class MassDelete extends TermController implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/NewAction.php b/app/code/Magento/Search/Controller/Adminhtml/Term/NewAction.php index 8565c03724c..49805400713 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/NewAction.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/NewAction.php @@ -5,10 +5,11 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Search\Controller\Adminhtml\Term as TermController; use Magento\Framework\Controller\ResultFactory; -class NewAction extends TermController +class NewAction extends TermController implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Forward diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Report.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Report.php index fb4964717e5..b0ce066f127 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Report.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Report.php @@ -5,10 +5,11 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Reports\Controller\Adminhtml\Index as ReportsIndexController; use Magento\Framework\Controller\ResultFactory; -class Report extends ReportsIndexController +class Report extends ReportsIndexController implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php index cd9b1347ed1..39ae07f0d35 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php @@ -5,13 +5,14 @@ */ namespace Magento\Search\Controller\Adminhtml\Term; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\Controller\ResultFactory; use Magento\Search\Model\QueryFactory; use Magento\Search\Controller\Adminhtml\Term as TermController; use Magento\Framework\Exception\LocalizedException; -class Save extends TermController +class Save extends TermController implements HttpPostActionInterface { /** * @var QueryFactory diff --git a/app/code/Magento/Search/Controller/Ajax/Suggest.php b/app/code/Magento/Search/Controller/Ajax/Suggest.php index 307f4df1632..6eab0949145 100644 --- a/app/code/Magento/Search/Controller/Ajax/Suggest.php +++ b/app/code/Magento/Search/Controller/Ajax/Suggest.php @@ -5,12 +5,13 @@ */ namespace Magento\Search\Controller\Ajax; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; use Magento\Search\Model\AutocompleteInterface; use Magento\Framework\Controller\ResultFactory; -class Suggest extends Action +class Suggest extends Action implements HttpGetActionInterface { /** * @var \Magento\Search\Model\AutocompleteInterface diff --git a/app/code/Magento/Search/view/frontend/web/js/form-mini.js b/app/code/Magento/Search/view/frontend/web/js/form-mini.js index 86d430041d7..15bcf2e7339 100644 --- a/app/code/Magento/Search/view/frontend/web/js/form-mini.js +++ b/app/code/Magento/Search/view/frontend/web/js/form-mini.js @@ -306,12 +306,13 @@ define([ dropdown.append(html); }); + this._resetResponseList(true); + this.responseList.indexList = this.autoComplete.html(dropdown) .css(clonePosition) .show() .find(this.options.responseFieldElements + ':visible'); - this._resetResponseList(false); this.element.removeAttr('aria-activedescendant'); if (this.responseList.indexList.length) { @@ -338,6 +339,11 @@ define([ this._resetResponseList(false); } }.bind(this)); + } else { + this._resetResponseList(true); + this.autoComplete.hide(); + this._updateAriaHasPopup(false); + this.element.removeAttr('aria-activedescendant'); } }, this)); } else { diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/NewAction.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/NewAction.php index be0555fbcda..7231bc8b9c6 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/NewAction.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/NewAction.php @@ -6,10 +6,11 @@ */ namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; use Magento\Framework\App\ObjectManager; -class NewAction extends \Magento\Backend\App\Action +class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php index 4804efcc76e..8bd64ccf82d 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php @@ -6,6 +6,7 @@ */ namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator; @@ -13,7 +14,7 @@ * Class Save * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\Backend\App\Action +class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Start.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Start.php index ac15c0accd1..8a874ddc795 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Start.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Start.php @@ -6,7 +6,9 @@ */ namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; -class Start extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Start extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml index 6c7e5cf1b18..6d877dac5cb 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml @@ -13,6 +13,13 @@ <entity name="flatRateActiveEnable" type="active"> <data key="value">1</data> </entity> + <!-- Disable Flat Rate Shipping method config --> + <entity name="DisableFlatRateShippingMethodConfig" type="flat_rate_shipping_method"> + <requiredEntity type="active">flatRateActiveDisable</requiredEntity> + </entity> + <entity name="flatRateActiveDisable" type="active"> + <data key="value">0</data> + </entity> <!-- Flat Rate Shipping method default setup --> <entity name="FlatRateShippingMethodDefault" type="flat_rate_shipping_method"> <requiredEntity type="active">flatRateActiveDefault</requiredEntity> diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml index 11079553346..512ef8389bb 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml @@ -52,4 +52,18 @@ <entity name="freeSortOrderDefault" type="sort_order"> <data key="value" /> </entity> + <!--Set Free Shipping Subtotal to 101--> + <entity name="setFreeShippingSubtotal" type="free_shipping_method"> + <requiredEntity type="free_shipping_subtotal">freeShippingSubtotal</requiredEntity> + </entity> + <entity name="freeShippingSubtotal" type="free_shipping_subtotal"> + <data key="value">101</data> + </entity> + <!--Set to default Free Shipping Subtotal--> + <entity name="setFreeShippingSubtotalToDefault" type="free_shipping_method"> + <requiredEntity type="free_shipping_subtotal">freeShippingSubtotalDefault</requiredEntity> + </entity> + <entity name="freeShippingSubtotalDefault" type="free_shipping_subtotal"> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php b/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php index 870705db941..168ab67f8cf 100644 --- a/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php +++ b/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php @@ -5,8 +5,8 @@ */ namespace Magento\Signifyd\Model\CaseServices; +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\NotFoundException; use Magento\Signifyd\Api\CaseRepositoryInterface; use Magento\Signifyd\Api\Data\CaseInterface; use Magento\Signifyd\Model\CommentsHistoryUpdater; @@ -73,7 +73,6 @@ public function __construct( * @param CaseInterface $case * @param array $data * @return void - * @throws NotFoundException * @throws LocalizedException */ public function update(CaseInterface $case, array $data) @@ -111,7 +110,7 @@ private function setCaseData(CaseInterface $case, array $data) 'orderId' ]; foreach ($data as $key => $value) { - $methodName = 'set' . ucfirst($key); + $methodName = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key); if (!in_array($key, $notResolvedKeys) && method_exists($case, $methodName)) { call_user_func([$case, $methodName], $value); } diff --git a/app/code/Magento/Signifyd/etc/db_schema.xml b/app/code/Magento/Signifyd/etc/db_schema.xml index 6a31eacbb45..6f41b883c71 100644 --- a/app/code/Magento/Signifyd/etc/db_schema.xml +++ b/app/code/Magento/Signifyd/etc/db_schema.xml @@ -37,6 +37,7 @@ </constraint> </table> <table name="sales_order_grid" resource="sales" comment="Sales Flat Order Grid"> - <column xsi:type="varchar" name="signifyd_guarantee_status" nullable="true" length="32"/> + <column xsi:type="varchar" name="signifyd_guarantee_status" nullable="true" length="32" + comment="Signifyd Guarantee Disposition Status"/> </table> </schema> diff --git a/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php b/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php index ffc2bf5f6d1..b4e54104bdf 100644 --- a/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php +++ b/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php @@ -3,17 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -/** - * Sitemap grid link column renderer - * - */ namespace Magento\Sitemap\Block\Adminhtml\Grid\Renderer; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot; use Magento\Framework\App\ObjectManager; +/** + * Sitemap grid link column renderer + */ class Link extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** @@ -62,6 +60,7 @@ public function render(\Magento\Framework\DataObject $row) { /** @var $sitemap \Magento\Sitemap\Model\Sitemap */ $sitemap = $this->_sitemapFactory->create(); + $sitemap->setStoreId($row->getStoreId()); $url = $this->escapeHtml($sitemap->getSitemapUrl($row->getSitemapPath(), $row->getSitemapFilename())); $fileName = preg_replace('/^\//', '', $row->getSitemapPath() . $row->getSitemapFilename()); diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Index.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Index.php index 5f2cc805de0..e6823c6070a 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Index.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Index.php @@ -6,9 +6,10 @@ */ namespace Magento\Sitemap\Controller\Adminhtml\Sitemap; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Backend\App\Action; -class Index extends \Magento\Sitemap\Controller\Adminhtml\Sitemap +class Index extends \Magento\Sitemap\Controller\Adminhtml\Sitemap implements HttpGetActionInterface { /** * Index action diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index 2bff20e0543..8f859ff3cd6 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -41,12 +41,16 @@ </entity> <entity name="staticStore" type="store"> <!--data key="group_id">customStoreGroup.id</data--> - <data key="name" >Second Store View</data> - <data key="code" >store123</data> + <data key="name">Second Store View</data> + <data key="code">store123</data> <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> <data key="store_type">store</data> <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> + <entity name="staticSecondStore" extends="staticStore"> + <data key="name">Store View</data> + <data key="code">store2</data> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml index 83ca12875d0..7b31623f237 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml @@ -27,4 +27,8 @@ <data key="root_category_id">2</data> <data key="website_id">1</data> </entity> + <entity name="staticFirstStoreGroup" extends="staticStoreGroup"> + <data key="name">NewStore</data> + <data key="code">Base1</data> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StorePaymentMethodsData.xml b/app/code/Magento/Store/Test/Mftf/Data/StorePaymentMethodsData.xml new file mode 100644 index 00000000000..912399142fa --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/StorePaymentMethodsData.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="PaymentMethodsSettingConfig" type="zero_subtotal_checkout_config_state"> + <requiredEntity type="active">active</requiredEntity> + <requiredEntity type="order_status">orderStatus</requiredEntity> + </entity> + <entity name="active" type="active"> + <data key="value">1</data> + </entity> + <entity name="orderStatus" type="order_status"> + <data key="value">processing</data> + </entity> + + <entity name="DisablePaymentMethodsSettingConfig" type="use_system_value_config_state"> + <requiredEntity type="zeroSubEnable">zeroSubEnable</requiredEntity> + <requiredEntity type="zeroSubOrderStatus">zeroSubOrderStatus</requiredEntity> + </entity> + <entity name="zeroSubEnable" type="zeroSubEnable"> + <data key="value">1</data> + </entity> + <entity name="zeroSubOrderStatus" type="zeroSubOrderStatus"> + <data key="value">1</data> + </entity> +</entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml new file mode 100644 index 00000000000..6e3e7ce7eb0 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="FreeShippingMethodsSettingConfig" type="free_shipping_config_state"> + <requiredEntity type="active">active</requiredEntity> + </entity> + <entity name="active" type="active"> + <data key="value">1</data> + </entity> + + <!-- default configuration used to restore Magento config --> + <entity name="DefaultShippingMethodsConfig" type="free_shipping_config_state"> + <requiredEntity type="active">DefaultFreeShipping</requiredEntity> + </entity> + <entity name="DefaultFreeShipping" type="active"> + <data key="value">0</data> + </entity> + + <entity name="DisableFreeShippingConfig" type="disable_free_shipping_config_state"> + <requiredEntity type="disableFreeShipping">disableFreeShipping</requiredEntity> + </entity> + <entity name="disableFreeShipping" type="disableFreeShipping"> + <data key="value">1</data> + </entity> +</entities> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store_payment_methods-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store_payment_methods-meta.xml new file mode 100644 index 00000000000..cbad7265cbb --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store_payment_methods-meta.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="EnableZeroSubtotalCheckoutConfigState" dataType="zero_subtotal_checkout_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/payment/" method="POST"> + <object key="groups" dataType="zero_subtotal_checkout_config_state"> + <object key="free" dataType="zero_subtotal_checkout_config_state"> + <object key="fields" dataType="zero_subtotal_checkout_config_state"> + <object key="active" dataType="active"> + <field key="value">string</field> + </object> + <object key="order_status" dataType="order_status"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> + + <operation name="DisablePaymentMethodsSettingConfig" dataType="use_system_value_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/payment/" method="POST"> + <object key="groups" dataType="use_system_value_config_state"> + <object key="free" dataType="use_system_value_config_state"> + <object key="fields" dataType="use_system_value_config_state"> + <object key="active" dataType="use_system_value_config_state"> + <object key="inherit" dataType="zeroSubEnable"> + <field key="value">integer</field> + </object> + </object> + <object key="order_status" dataType="use_system_value_config_state"> + <object key="inherit" dataType="zeroSubOrderStatus"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </object> + </operation> + +</operations> + diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml new file mode 100644 index 00000000000..83288ecfdaf --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="EnableFreeShippingConfigState" dataType="free_shipping_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/carriers/" method="POST"> + <object key="groups" dataType="free_shipping_config_state"> + <object key="freeshipping" dataType="free_shipping_config_state"> + <object key="fields" dataType="free_shipping_config_state"> + <object key="active" dataType="active"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> + + <operation name="DisableFreeShippingConfigState" dataType="disable_free_shipping_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/carriers/" method="POST"> + <object key="groups" dataType="disable_free_shipping_config_state"> + <object key="freeshipping" dataType="disable_free_shipping_config_state"> + <object key="fields" dataType="disable_free_shipping_config_state"> + <object key="active" dataType="disable_free_shipping_config_state"> + <object key="inherit" dataType="disableFreeShipping"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </object> + </operation> + +</operations> + diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php index 92926c12e86..8c2d6c36591 100644 --- a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php @@ -7,10 +7,10 @@ namespace Magento\StoreGraphQl\Model\Resolver\Store; -use Magento\Store\Api\Data\StoreConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Store\Api\StoreConfigManagerInterface; -use Magento\Store\Api\StoreRepositoryInterface; -use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; /** * StoreConfig field data provider, used for GraphQL request processing. @@ -23,39 +23,60 @@ class StoreConfigDataProvider private $storeConfigManager; /** - * @var StoreResolverInterface + * @var StoreManagerInterface */ - private $storeResolver; + private $storeManager; /** - * @var StoreRepositoryInterface + * @var ScopeConfigInterface */ - private $storeRepository; + private $scopeConfig; + + /** + * @var array + */ + private $extendedConfigData; /** * @param StoreConfigManagerInterface $storeConfigManager - * @param StoreResolverInterface $storeResolver - * @param StoreRepositoryInterface $storeRepository + * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig + * @param array $extendedConfigData */ public function __construct( StoreConfigManagerInterface $storeConfigManager, - StoreResolverInterface $storeResolver, - StoreRepositoryInterface $storeRepository + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig, + array $extendedConfigData = [] ) { $this->storeConfigManager = $storeConfigManager; - $this->storeResolver = $storeResolver; - $this->storeRepository = $storeRepository; + $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; + $this->extendedConfigData = $extendedConfigData; + } + + /** + * Get store config data + * + * @return array + */ + public function getStoreConfigData(): array + { + $storeConfigData = array_merge( + $this->getBaseConfigData(), + $this->getExtendedConfigData() + ); + return $storeConfigData; } /** - * Get store config for current store + * Get base config data * * @return array */ - public function getStoreConfig() : array + private function getBaseConfigData() : array { - $storeId = $this->storeResolver->getCurrentStoreId(); - $store = $this->storeRepository->getById($storeId); + $store = $this->storeManager->getStore(); $storeConfig = current($this->storeConfigManager->getStoreConfigs([$store->getCode()])); $storeConfigData = [ @@ -78,4 +99,23 @@ public function getStoreConfig() : array ]; return $storeConfigData; } + + /** + * Get extended config data + * + * @return array + */ + private function getExtendedConfigData() + { + $store = $this->storeManager->getStore(); + $extendedConfigData = []; + foreach ($this->extendedConfigData as $key => $path) { + $extendedConfigData[$key] = $this->scopeConfig->getValue( + $path, + ScopeInterface::SCOPE_STORE, + $store->getId() + ); + } + return $extendedConfigData; + } } diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php b/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php index 39fcd1bf279..9c426172de8 100644 --- a/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php @@ -23,12 +23,12 @@ class StoreConfigResolver implements ResolverInterface private $storeConfigDataProvider; /** - * @param StoreConfigDataProvider $storeConfigDataProvider + * @param StoreConfigDataProvider $storeConfigsDataProvider */ public function __construct( - StoreConfigDataProvider $storeConfigDataProvider + StoreConfigDataProvider $storeConfigsDataProvider ) { - $this->storeConfigDataProvider = $storeConfigDataProvider; + $this->storeConfigDataProvider = $storeConfigsDataProvider; } /** @@ -41,8 +41,6 @@ public function resolve( array $value = null, array $args = null ) { - - $storeConfigData = $this->storeConfigDataProvider->getStoreConfig(); - return $storeConfigData; + return $this->storeConfigDataProvider->getStoreConfigData(); } } diff --git a/app/code/Magento/StoreGraphQl/composer.json b/app/code/Magento/StoreGraphQl/composer.json index d03d759babd..d53ba9fbb00 100644 --- a/app/code/Magento/StoreGraphQl/composer.json +++ b/app/code/Magento/StoreGraphQl/composer.json @@ -8,8 +8,7 @@ "magento/module-store": "*" }, "suggest": { - "magento/module-graph-ql": "*", - "magento/module-catalog-graph-ql": "*" + "magento/module-graph-ql": "*" }, "license": [ "OSL-3.0", diff --git a/app/code/Magento/Swatches/Controller/Ajax/Media.php b/app/code/Magento/Swatches/Controller/Ajax/Media.php index 079ba8f8971..7692245e9b2 100644 --- a/app/code/Magento/Swatches/Controller/Ajax/Media.php +++ b/app/code/Magento/Swatches/Controller/Ajax/Media.php @@ -12,7 +12,7 @@ /** * Class Media */ -class Media extends \Magento\Framework\App\Action\Action +class Media extends \Magento\Framework\App\Action\Action implements \Magento\Framework\App\Action\HttpGetActionInterface { /** * @var \Magento\Catalog\Model\Product Factory @@ -24,18 +24,26 @@ class Media extends \Magento\Framework\App\Action\Action */ private $swatchHelper; + /** + * @var \Magento\PageCache\Model\Config + */ + protected $config; + /** * @param Context $context * @param \Magento\Catalog\Model\ProductFactory $productModelFactory * @param \Magento\Swatches\Helper\Data $swatchHelper + * @param \Magento\PageCache\Model\Config $config */ public function __construct( Context $context, \Magento\Catalog\Model\ProductFactory $productModelFactory, - \Magento\Swatches\Helper\Data $swatchHelper + \Magento\Swatches\Helper\Data $swatchHelper, + \Magento\PageCache\Model\Config $config ) { $this->productModelFactory = $productModelFactory; $this->swatchHelper = $swatchHelper; + $this->config = $config; parent::__construct($context); } @@ -48,14 +56,23 @@ public function __construct( public function execute() { $productMedia = []; + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + + /** @var \Magento\Framework\App\ResponseInterface $response */ + $response = $this->getResponse(); + if ($productId = (int)$this->getRequest()->getParam('product_id')) { + $product = $this->productModelFactory->create()->load($productId); $productMedia = $this->swatchHelper->getProductMediaGallery( - $this->productModelFactory->create()->load($productId) + $product ); + $resultJson->setHeader('X-Magento-Tags', implode(',', $product->getIdentities())); + + $response->setPublicHeaders($this->config->getTtl()); } - /** @var \Magento\Framework\Controller\Result\Json $resultJson */ - $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); $resultJson->setData($productMedia); return $resultJson; } diff --git a/app/code/Magento/Swatches/Test/Unit/Controller/Ajax/MediaTest.php b/app/code/Magento/Swatches/Test/Unit/Controller/Ajax/MediaTest.php index 7a110c63da7..5a11e2787bc 100644 --- a/app/code/Magento/Swatches/Test/Unit/Controller/Ajax/MediaTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Controller/Ajax/MediaTest.php @@ -20,6 +20,9 @@ class MediaTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Catalog\Model\ProductFactory|\PHPUnit_Framework_MockObject_MockObject */ private $productModelFactoryMock; + /** @var \Magento\PageCache\Model\Config|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ private $productMock; @@ -29,6 +32,9 @@ class MediaTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ private $requestMock; + /** @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $responseMock; + /** @var \Magento\Framework\Controller\ResultFactory|\PHPUnit_Framework_MockObject_MockObject */ private $resultFactory; @@ -57,11 +63,20 @@ protected function setUp() \Magento\Catalog\Model\ProductFactory::class, ['create'] ); + $this->config = $this->createMock(\Magento\PageCache\Model\Config::class); + $this->config->method('getTtl')->willReturn(1); + $this->productMock = $this->createMock(\Magento\Catalog\Model\Product::class); $this->contextMock = $this->createMock(\Magento\Framework\App\Action\Context::class); $this->requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); $this->contextMock->method('getRequest')->willReturn($this->requestMock); + $this->responseMock = $this->getMockBuilder(\Magento\Framework\App\ResponseInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setPublicHeaders']) + ->getMockForAbstractClass(); + $this->responseMock->method('setPublicHeaders')->willReturnSelf(); + $this->contextMock->method('getResponse')->willReturn($this->responseMock); $this->resultFactory = $this->createPartialMock(\Magento\Framework\Controller\ResultFactory::class, ['create']); $this->contextMock->method('getResultFactory')->willReturn($this->resultFactory); @@ -73,7 +88,8 @@ protected function setUp() [ 'context' => $this->contextMock, 'swatchHelper' => $this->swatchHelperMock, - 'productModelFactory' => $this->productModelFactoryMock + 'productModelFactory' => $this->productModelFactoryMock, + 'config' => $this->config ] ); } @@ -86,6 +102,10 @@ public function testExecute() ->method('load') ->with(59) ->willReturn($this->productMock); + $this->productMock + ->expects($this->once()) + ->method('getIdentities') + ->willReturn(['tags']); $this->productModelFactoryMock ->expects($this->once()) diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index 0940003b6a7..09d3e05008a 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -13,6 +13,7 @@ "magento/module-configurable-product": "*", "magento/module-customer": "*", "magento/module-eav": "*", + "magento/module-page-cache": "*", "magento/module-media-storage": "*", "magento/module-store": "*", "magento/module-theme": "*" diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js index 01411523108..46083cd9fb4 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js @@ -45,7 +45,7 @@ define([ get tabsFront() { return this.attrTabsFront.length ? this.attrTabsFront.closest('li') : $('#front_fieldset-wrapper'); }, - selectFields: ['select', 'multiselect', 'price', 'swatch_text', 'swatch_visual'], + selectFields: ['boolean', 'select', 'multiselect', 'price', 'swatch_text', 'swatch_visual'], /** * @this {swatchProductAttributes} diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index 853eba3c98d..63dbd31751e 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -1029,12 +1029,14 @@ define([ mediaCallData.isAjax = true; $widget._XhrKiller(); $widget._EnableProductMediaLoader($this); - $widget.xhr = $.get( - $widget.options.mediaCallback, - mediaCallData, - mediaSuccessCallback, - 'json' - ).done(function () { + $widget.xhr = $.ajax({ + url: $widget.options.mediaCallback, + cache: true, + type: 'GET', + dataType: 'json', + data: mediaCallData, + success: mediaSuccessCallback + }).done(function () { $widget._XhrKiller(); }); } diff --git a/app/code/Magento/Tax/Controller/Adminhtml/Rate/Index.php b/app/code/Magento/Tax/Controller/Adminhtml/Rate/Index.php index b91eef7b6f7..565af0e0bf9 100644 --- a/app/code/Magento/Tax/Controller/Adminhtml/Rate/Index.php +++ b/app/code/Magento/Tax/Controller/Adminhtml/Rate/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Tax\Controller\Adminhtml\Rate; -class Index extends \Magento\Tax\Controller\Adminhtml\Rate +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Tax\Controller\Adminhtml\Rate implements HttpGetActionInterface { /** * Show Main Grid diff --git a/app/code/Magento/Tax/Controller/Adminhtml/Rule/Delete.php b/app/code/Magento/Tax/Controller/Adminhtml/Rule/Delete.php index 71b6d7bf393..1e46f0ea3d2 100644 --- a/app/code/Magento/Tax/Controller/Adminhtml/Rule/Delete.php +++ b/app/code/Magento/Tax/Controller/Adminhtml/Rule/Delete.php @@ -6,9 +6,10 @@ */ namespace Magento\Tax\Controller\Adminhtml\Rule; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class Delete extends \Magento\Tax\Controller\Adminhtml\Rule +class Delete extends \Magento\Tax\Controller\Adminhtml\Rule implements HttpPostActionInterface { /** * @return \Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Tax/Controller/Adminhtml/Rule/Edit.php b/app/code/Magento/Tax/Controller/Adminhtml/Rule/Edit.php index dc0f5188025..740d7e8afe6 100644 --- a/app/code/Magento/Tax/Controller/Adminhtml/Rule/Edit.php +++ b/app/code/Magento/Tax/Controller/Adminhtml/Rule/Edit.php @@ -6,9 +6,10 @@ */ namespace Magento\Tax\Controller\Adminhtml\Rule; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; -class Edit extends \Magento\Tax\Controller\Adminhtml\Rule +class Edit extends \Magento\Tax\Controller\Adminhtml\Rule implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page|\Magento\Backend\Model\View\Result\Redirect diff --git a/app/code/Magento/Tax/Controller/Adminhtml/Rule/Index.php b/app/code/Magento/Tax/Controller/Adminhtml/Rule/Index.php index ddf6099a4f8..fd774777e3e 100644 --- a/app/code/Magento/Tax/Controller/Adminhtml/Rule/Index.php +++ b/app/code/Magento/Tax/Controller/Adminhtml/Rule/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Tax\Controller\Adminhtml\Rule; -class Index extends \Magento\Tax\Controller\Adminhtml\Rule +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Tax\Controller\Adminhtml\Rule implements HttpGetActionInterface { /** * @return \Magento\Backend\Model\View\Result\Page diff --git a/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php b/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php index ebf699db552..60cb6fe2898 100644 --- a/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php +++ b/app/code/Magento/Tax/Model/ResourceModel/Report/Tax/Createdat.php @@ -11,6 +11,9 @@ */ namespace Magento\Tax\Model\ResourceModel\Report\Tax; +/** + * Class for tax report resource model with aggregation by created at + */ class Createdat extends \Magento\Reports\Model\ResourceModel\Report\AbstractReport { /** @@ -84,7 +87,7 @@ protected function _aggregateByOrder($aggregationField, $from, $to) 'order_status' => 'e.status', 'percent' => 'MAX(tax.' . $connection->quoteIdentifier('percent') . ')', 'orders_count' => 'COUNT(DISTINCT e.entity_id)', - 'tax_base_amount_sum' => 'SUM(tax.base_amount * e.base_to_global_rate)', + 'tax_base_amount_sum' => 'SUM(tax.base_real_amount * e.base_to_global_rate)', ]; $select = $connection->select()->from( diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml index d6e5f4b693c..aaea515aa90 100644 --- a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml @@ -16,14 +16,18 @@ <!-- change the default state to California --> <scrollTo selector="#tax_defaults-head" x="0" y="-80" stepKey="scrollToTaxDefaults" /> + <!-- conditionalClick twice to fix some flaky behavior --> <conditionalClick stepKey="clickCalculationSettings" selector="{{AdminConfigureTaxSection.defaultDestination}}" dependentSelector="#tax_defaults" visible="false" /> + <conditionalClick stepKey="clickCalculationSettingsAgain" selector="{{AdminConfigureTaxSection.defaultDestination}}" dependentSelector="#tax_defaults" visible="false" /> <uncheckOption stepKey="clickDefaultState" selector="{{AdminConfigureTaxSection.systemValueDefaultState}}"/> <selectOption stepKey="selectDefaultState" selector="{{AdminConfigureTaxSection.dropdownDefaultState}}" userInput="California"/> <fillField stepKey="fillDefaultPostCode" selector="{{AdminConfigureTaxSection.defaultPostCode}}" userInput="*"/> <!-- change the options for shopping cart display to show tax --> <scrollTo selector="#tax_cart_display-head" x="0" y="-80" stepKey="scrollToTaxShoppingCartDisplay" /> + <!-- conditionalClick twice to fix some flaky behavior --> <conditionalClick stepKey="clickShoppingCartDisplaySettings" selector="{{AdminConfigureTaxSection.shoppingCartDisplay}}" dependentSelector="#tax_cart_display" visible="false"/> + <conditionalClick stepKey="clickShoppingCartDisplaySettingsAgain" selector="{{AdminConfigureTaxSection.shoppingCartDisplay}}" dependentSelector="#tax_cart_display" visible="false"/> <uncheckOption stepKey="clickTaxTotalCart" selector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalCart}}"/> <selectOption stepKey="selectTaxTotalCart" selector="{{AdminConfigureTaxSection.dropdownIncludeTaxTotalCart}}" userInput="Yes"/> <uncheckOption stepKey="clickDisplayTaxSummaryCart" selector="{{AdminConfigureTaxSection.systemValueDisplayTaxSummaryCart}}"/> @@ -33,7 +37,9 @@ <!-- change the options for orders, invoices, credit memos display to show tax --> <scrollTo selector="#tax_sales_display-head" x="0" y="-80" stepKey="scrollToTaxSalesDisplay" /> + <!-- conditionalClick twice to fix some flaky behavior --> <conditionalClick stepKey="clickOrdersInvoicesCreditSales" selector="{{AdminConfigureTaxSection.ordersInvoicesCreditSales}}" dependentSelector="#tax_sales_display" visible="false"/> + <conditionalClick stepKey="clickOrdersInvoicesCreditSalesAgain" selector="{{AdminConfigureTaxSection.ordersInvoicesCreditSales}}" dependentSelector="#tax_sales_display" visible="false"/> <uncheckOption stepKey="clickTaxTotalSales" selector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalSales}}"/> <selectOption stepKey="selectTaxTotalSales" selector="{{AdminConfigureTaxSection.dropdownIncludeTaxTotalSales}}" userInput="Yes"/> <uncheckOption stepKey="clickDisplayTaxSummarySales" selector="{{AdminConfigureTaxSection.systemValueDisplayTaxSummarySales}}"/> @@ -54,13 +60,17 @@ <waitForPageLoad stepKey="waitForTaxConfigLoad"/> <!-- change the default state to none --> + <!-- conditionalClick twice to fix some flaky behavior --> <conditionalClick stepKey="clickCalculationSettings" selector="{{AdminConfigureTaxSection.defaultDestination}}" dependentSelector="{{AdminConfigureTaxSection.systemValueDefaultState}}" visible="false" /> + <conditionalClick stepKey="clickCalculationSettingsAgain" selector="{{AdminConfigureTaxSection.defaultDestination}}" dependentSelector="{{AdminConfigureTaxSection.systemValueDefaultState}}" visible="false" /> <checkOption stepKey="clickDefaultState" selector="{{AdminConfigureTaxSection.systemValueDefaultState}}"/> <selectOption stepKey="selectDefaultState" selector="{{AdminConfigureTaxSection.dropdownDefaultState}}" userInput="California"/> <fillField stepKey="fillDefaultPostCode" selector="{{AdminConfigureTaxSection.defaultPostCode}}" userInput=""/> <!-- change the options for shopping cart display to not show tax --> + <!-- conditionalClick twice to fix some flaky behavior --> <conditionalClick stepKey="clickShoppingCartDisplaySettings" selector="{{AdminConfigureTaxSection.shoppingCartDisplay}}" dependentSelector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalCart}}" visible="false"/> + <conditionalClick stepKey="clickShoppingCartDisplaySettingsAgain" selector="{{AdminConfigureTaxSection.shoppingCartDisplay}}" dependentSelector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalCart}}" visible="false"/> <checkOption stepKey="clickTaxTotalCart" selector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalCart}}"/> <selectOption stepKey="selectTaxTotalCart" selector="{{AdminConfigureTaxSection.dropdownIncludeTaxTotalCart}}" userInput="Yes"/> <checkOption stepKey="clickDisplayTaxSummaryCart" selector="{{AdminConfigureTaxSection.systemValueDisplayTaxSummaryCart}}"/> @@ -69,7 +79,9 @@ <selectOption stepKey="selectDisplayZeroTaxCart" selector="{{AdminConfigureTaxSection.dropdownDisplayZeroTaxCart}}" userInput="Yes"/> <!-- change the options for orders, invoices, credit memos display to not show tax --> + <!-- conditionalClick twice to fix some flaky behavior --> <conditionalClick stepKey="clickOrdersInvoicesCreditSales" selector="{{AdminConfigureTaxSection.ordersInvoicesCreditSales}}" dependentSelector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalSales}}" visible="false"/> + <conditionalClick stepKey="clickOrdersInvoicesCreditSalesAgain" selector="{{AdminConfigureTaxSection.ordersInvoicesCreditSales}}" dependentSelector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalSales}}" visible="false"/> <checkOption stepKey="clickTaxTotalSales" selector="{{AdminConfigureTaxSection.systemValueIncludeTaxTotalSales}}"/> <selectOption stepKey="selectTaxTotalSales" selector="{{AdminConfigureTaxSection.dropdownIncludeTaxTotalSales}}" userInput="Yes"/> <checkOption stepKey="clickDisplayTaxSummarySales" selector="{{AdminConfigureTaxSection.systemValueDisplayTaxSummarySales}}"/> @@ -103,4 +115,44 @@ <!-- Save the tax rate --> <click stepKey="saveTaxRate" selector="{{AdminTaxRulesSection.save}}"/> </actionGroup> + + <!--Add Product Tax Class--> + <actionGroup name="addProductTaxClass"> + <arguments> + <argument name="prodTaxClassName" type="string"/> + </arguments> + <!--Click Additional Settings--> + <click stepKey="clickAdditionalSettings" selector="{{AdminTaxRulesSection.additionalSettings}}"/> + <!--Click Product Add New Tax Class Button--> + <click stepKey="clickProdAddNewTaxClassBtn" selector="{{AdminTaxRulesSection.productAddNewTaxClass}}"/> + <!--Fill field--> + <fillField stepKey="fillProdNewTaxClass" selector="{{AdminTaxRulesSection.fieldProdNewTaxClass}}" userInput="{{prodTaxClassName}}"/> + <!-- Save Product tax rate --> + <click stepKey="saveProdTaxRate" selector="{{AdminTaxRulesSection.saveProdNewTaxClass}}"/> + </actionGroup> + + <!--Add New Tax Rule --> + <actionGroup name="addNewTaxRuleActionGroup"> + <!-- Go to tax rule page --> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRulePage"/> + <waitForPageLoad stepKey="waitForTaxRatePage"/> + <click stepKey="addNewTaxRate" selector="{{AdminGridMainControls.add}}"/> + </actionGroup> + + <!--Delete Product Tax Class--> + <actionGroup name="deleteProductTaxClass"> + <arguments> + <argument name="taxClassName" type="string"/> + </arguments> + <!-- Go to tax rule page --> + <amOnPage url="{{AdminNewTaxRulePage.url}}" stepKey="goToNewTaxRulePage"/> + <waitForPageLoad stepKey="waitForTaxRatePage"/> + <click stepKey="clickAdditionalSettings" selector="{{AdminTaxRulesSection.additionalSettings}}"/> + <scrollTo stepKey="scrollToAdditionalSettings" selector="{{AdminTaxRulesSection.additionalSettings}}"/> + <moveMouseOver stepKey="hoverDeleteElement" selector="{{AdminTaxRulesSection.deleteTaxClassName(taxClassName)}}"/> + <click stepKey="deleteFirstTaxClass" selector="{{AdminTaxRulesSection.deleteTaxClass(taxClassName)}}"/> + <waitForElementVisible selector="{{AdminTaxRulesSection.popUpDialogOK}}" stepKey="waitForElementBecomeVisible"/> + <click stepKey="acceptPopUpDialog" selector="{{AdminTaxRulesSection.popUpDialogOK}}"/> + <waitForPageLoad stepKey="waitForProductTaxClassDeleted"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml index 42fd0135737..36929ef2e3f 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml @@ -26,4 +26,16 @@ <data key="zip">*</data> <data key="rate">0</data> </entity> + <entity name="SimpleTaxWithZipCode" type="tax"> + <data key="state">Texas</data> + <data key="country">United States</data> + <data key="zip">78729</data> + <data key="rate">7.25</data> + </entity> + <entity name="SimpleSecondTaxWithZipCode" type="tax"> + <data key="state">Texas</data> + <data key="country">United States</data> + <data key="zip">78729</data> + <data key="rate">0.125</data> + </entity> </entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml new file mode 100644 index 00000000000..80101687e17 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminTaxReportsSection"> + <element name="refreshStatistics" type="button" selector="//a[contains(text(),'here')]"/> + <element name="fromDate" type="input" selector="#sales_report_from"/> + <element name="toDate" type="input" selector="//*[@id='sales_report_to']/following-sibling::button"/> + <element name="goTodayButton" type="input" selector="//button[contains(text(),'Go Today')]"/> + <element name="showReportButton" type="button" selector="#filter_form_submit"/> + <element name="taxRuleAmount" type="textarea" selector="//*[contains(text(),'{{var1}}')]/following-sibling::td[contains(@class, 'col-tax-amount')]" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml index 9727c649d7e..6c258f2de18 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml @@ -19,5 +19,14 @@ <element name="country" type="select" selector="#tax_country_id"/> <element name="rate" type="input" selector="#rate"/> <element name="save" type="button" selector=".action-save" timeout="30"/> + <element name="saveRule" type="button" selector="#save" timeout="30"/> + <element name="additionalSettings" type="button" selector="#details-summarybase_fieldset"/> + <element name="productAddNewTaxClass" type="button" selector="//*[@id='tax_product_class']/following-sibling::section//*[contains(text(),'Add New Tax Class')]"/> + <element name="fieldProdNewTaxClass" type="input" selector="//*[@id='tax_product_class']/following-sibling::section//input[@class='mselect-input']"/> + <element name="saveProdNewTaxClass" type="button" selector="//*[@id='tax_product_class']/following-sibling::section//span[@class='mselect-save']"/> + <element name="defaultTaxClass" type="button" selector="//*[@id='tax_product_class']/following-sibling::section//div[@class='mselect-items-wrapper']/div[1]"/> + <element name="deleteTaxClassName" type="button" selector="//span[contains(text(),'{{var1}}')]" parameterized="true"/> + <element name="deleteTaxClass" type="button" selector="//span[contains(text(),'{{var1}}')]/../..//*[@class='mselect-delete']" parameterized="true"/> + <element name="popUpDialogOK" type="button" selector="//*[@class='modal-footer']//*[contains(text(),'OK')]"/> </section> </sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml new file mode 100644 index 00000000000..68fe8087c4f --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminTaxReportGridTest"> + <annotations> + <features value="Tax"/> + <stories value="MAGETWO-91521: Reports / Sales / Tax report show incorrect amount"/> + <title value="Checking Tax Report grid"/> + <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94338"/> + <group value="Tax"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Go to tax rule page --> + <actionGroup ref="addNewTaxRuleActionGroup" stepKey="addFirstTaxRuleActionGroup"/> + <fillField stepKey="fillRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="TaxRule1"/> + + <!-- Add NY and CA tax rules --> + <actionGroup ref="addNewTaxRateNoZip" stepKey="addNYTaxRate"> + <argument name="taxCode" value="SimpleTaxWithZipCode"/> + </actionGroup> + + <actionGroup ref="addProductTaxClass" stepKey="addProductTaxClass"> + <argument name="prodTaxClassName" value="TaxClasses1"/> + </actionGroup> + + <click stepKey="disableDefaultProdTaxClass" selector="{{AdminTaxRulesSection.defaultTaxClass}}"/> + <waitForPageLoad stepKey="waitForTaxRulePage"/> + <click stepKey="clickSave" selector="{{AdminTaxRulesSection.saveRule}}"/> + <waitForPageLoad stepKey="waitForNewTaxRuleCreated"/> + + <!-- Go to tax rule page to create second Tax Rule--> + <actionGroup ref="addNewTaxRuleActionGroup" stepKey="addSecondTaxRuleActionGroup"/> + <fillField stepKey="fillSecondRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="TaxRule2"/> + + <actionGroup ref="addNewTaxRateNoZip" stepKey="addCATaxRate"> + <argument name="taxCode" value="SimpleSecondTaxWithZipCode"/> + </actionGroup> + + <actionGroup ref="addProductTaxClass" stepKey="addSecondProductTaxClass"> + <argument name="prodTaxClassName" value="TaxClasses2"/> + </actionGroup> + + <click stepKey="disableSecondProdTaxClass" selector="{{AdminTaxRulesSection.defaultTaxClass}}"/> + <waitForPageLoad stepKey="waitForTaxRulePage2"/> + <click stepKey="clickSaveBtn" selector="{{AdminTaxRulesSection.saveRule}}"/> + <waitForPageLoad stepKey="waitForSecondTaxRuleCreated"/> + + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="firstProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <createData entity="_defaultProduct" stepKey="secondProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + + <!--Open Created products. In Tax Class select new created Product Tax classes.--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndex"/> + <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGrid"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku"> + <argument name="product" value="$$firstProduct$$"/> + </actionGroup> + <actionGroup ref="openProducForEditByClickingRowXColumnYInProductGrid" stepKey="openFirstProductForEdit"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" stepKey="selectTexClassForFirstProduct" userInput="TaxClasses1"/> + <!-- Save the second product --> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveFirstProduct"/> + <waitForPageLoad stepKey="waitForFirstProductSaved"/> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="againGoToProductIndex"/> + <waitForPageLoad stepKey="wait2"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetSecondProductGrid"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterSecondProductGridBySku"> + <argument name="product" value="$$secondProduct$$"/> + </actionGroup> + <actionGroup ref="openProducForEditByClickingRowXColumnYInProductGrid" stepKey="openSecondProductForEdit"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" stepKey="selectTexClassForSecondProduct" userInput="TaxClasses2"/> + <!-- Save the second product --> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveSecondProduct"/> + <waitForPageLoad stepKey="waitForSecondProductSaved"/> + + <!--Create an order with these 2 products in that zip code.--> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> + <click selector="{{AdminOrderFormActionSection.CreateNewCustomer}}" stepKey="clickCreateCustomer"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + <waitForPageLoad stepKey="waitForPage" time="60"/> + <!--Check if order can be submitted without the required fields including email address--> + <scrollToTopOfPage stepKey="scrollToTopOfOrderFormPage" after="seeNewOrderPageTitle"/> + <actionGroup ref="addSimpleProductToOrder" stepKey="addFirstProductToOrder" after="scrollToTopOfOrderFormPage"> + <argument name="product" value="$$firstProduct$$"/> + </actionGroup> + <actionGroup ref="addSimpleProductToOrder" stepKey="addSecondProductToOrder" after="addFirstProductToOrder"> + <argument name="product" value="$$secondProduct$$"/> + </actionGroup> + + <!--Fill customer group and customer email--> + <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectCustomerGroup" after="addSecondProductToOrder"/> + <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail" after="selectCustomerGroup"/> + + <!--Fill customer address information--> + <actionGroup ref="fillOrderCustomerInformation" stepKey="fillCustomerAddress" after="fillCustomerEmail"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_TX"/> + </actionGroup> + + <!-- Select shipping --> + <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping" after="fillCustomerAddress"/> + + <!--Submit Order and verify information--> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder" after="selectFlatRateShipping"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> + + <!--Create Invoice and Shipment for this Order.--> + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoiceInPageTitle" after="clickInvoiceButton"/> + <waitForPageLoad stepKey="waitForInvoicePageOpened"/> + + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + <waitForPageLoad stepKey="waitForInvoiceSaved"/> + + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction" after="waitForInvoiceSaved"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl" after="clickShipAction"/> + <!--Submit Shipment--> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment" after="seeOrderShipmentUrl"/> + <waitForPageLoad stepKey="waitForShipmentSaved"/> + + <!--Go to "Reports" -> "Sales" -> "Tax"--> + <amOnPage url="/admin/reports/report_sales/tax/" stepKey="navigateToReportsTaxPage"/> + <waitForPageLoad stepKey="waitForReportsTaxPageLoad"/> + + <!--click "here" to refresh last day's statistics --> + <click stepKey="clickRefrashStatisticsHere" selector="{{AdminTaxReportsSection.refreshStatistics}}"/> + <waitForPageLoad stepKey="waitForRefresh"/> + + <!--Select Dates--> + <fillField selector="{{AdminTaxReportsSection.fromDate}}" userInput="05/16/2018" stepKey="fillDateFrom"/> + <click selector="{{AdminTaxReportsSection.toDate}}" stepKey="clickDateTo"/> + <click selector="{{AdminTaxReportsSection.goTodayButton}}" stepKey="clickGoTodayDate"/> + <!--Click "Show report" in the upper right corner.--> + <click selector="{{AdminTaxReportsSection.showReportButton}}" stepKey="clickShowReportButton"/> + <waitForPageLoad time="60" stepKey="waitForReload"/> + <!--Tax Report Grid displays Tax amount in rows. "Total" and "Subtotal" is a sum of all tax amounts--> + <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Texas-0.125')}}" stepKey="amountOfFirstTaxRate"/> + <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Texas-7.25')}}" stepKey="amountOfSecondTaxRate"/> + <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Subtotal')}}" stepKey="amountOfSubtotalTaxRate"/> + <assertEquals stepKey="assertSubtotalFirstField"> + <expectedResult type="string">$0.15</expectedResult> + <actualResult type="variable">amountOfFirstTaxRate</actualResult> + </assertEquals> + + <assertEquals stepKey="assertSubtotalSecondField"> + <expectedResult type="string">$8.92</expectedResult> + <actualResult type="variable">amountOfSecondTaxRate</actualResult> + </assertEquals> + + <assertEquals stepKey="assertSubtotalField"> + <expectedResult type="string">$9.07</expectedResult> + <actualResult type="variable">amountOfSubtotalTaxRate</actualResult> + </assertEquals> + + <after> + <!-- Go to the tax rule page and delete the row we created--> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRulesPage"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteRule"> + <argument name="name" value="TaxRule1"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteSecondRule"> + <argument name="name" value="TaxRule2"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <!-- Go to the tax rate page --> + <amOnPage url="{{AdminTaxRateGridPage.url}}" stepKey="goToTaxRatesPage"/> + <waitForPageLoad stepKey="waitForRatesPage"/> + + <!-- Delete the two tax rates that were created --> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteNYRate"> + <argument name="name" value="{{SimpleTaxWithZipCode.state}}-{{SimpleTaxWithZipCode.rate}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteCARate"> + <argument name="name" value="{{SimpleSecondTaxWithZipCode.state}}-{{SimpleSecondTaxWithZipCode.rate}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <deleteData createDataKey="firstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="secondProduct" stepKey="deleteSecondProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <actionGroup ref="deleteProductTaxClass" stepKey="deleteFirstProductTaxClass"> + <argument name="taxClassName" value="TaxClasses1"/> + </actionGroup> + + <actionGroup ref="deleteProductTaxClass" stepKey="deleteSecondProductTaxClass"> + <argument name="taxClassName" value="TaxClasses2"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index 47161001219..9bc44dec0b5 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -16,10 +16,11 @@ <description value="Tax information are updating/recalculating on fly in shopping cart for Guest (physical quote)"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-41930"/> - <!--Skip because of issue MAGETWO-90966 on 4 step--> - <group value="skip"/> <group value="checkout"/> <group value="tax"/> + <skip> + <issueId value="MAGETWO-90966"/> + </skip> </annotations> <before> <!-- Preconditions --> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index 88496b8e2cd..06f6abb2973 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -16,8 +16,6 @@ <description value="Tax information are updating/recalculating on fly in shopping cart for Guest (virtual quote)"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-41931"/> - <!--Skip because of issue MAGETWO-90966 on 4 step--> - <group value="skip"/> <group value="checkout"/> <group value="tax"/> </annotations> diff --git a/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ImportExport.php b/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ImportExport.php index 929c9db01a2..4264f5c3b77 100644 --- a/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ImportExport.php +++ b/app/code/Magento/TaxImportExport/Controller/Adminhtml/Rate/ImportExport.php @@ -5,9 +5,10 @@ */ namespace Magento\TaxImportExport\Controller\Adminhtml\Rate; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; -class ImportExport extends \Magento\TaxImportExport\Controller\Adminhtml\Rate +class ImportExport extends \Magento\TaxImportExport\Controller\Adminhtml\Rate implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php index d51fd658075..1a136cca4d6 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php @@ -6,11 +6,13 @@ */ namespace Magento\Theme\Controller\Adminhtml\System\Design\Theme; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + /** * Class Index * @deprecated 100.2.0 */ -class Index extends \Magento\Theme\Controller\Adminhtml\System\Design\Theme +class Index extends \Magento\Theme\Controller\Adminhtml\System\Design\Theme implements HttpGetActionInterface { /** * Index action diff --git a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php index 82206b8eb1c..2c3350e695a 100644 --- a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php +++ b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php @@ -11,6 +11,11 @@ use Magento\Framework\App\Filesystem\DirectoryList; +/** + * Class Storage + * + * @package Magento\Theme\Model\Wysiwyg + */ class Storage { /** @@ -127,14 +132,6 @@ public function uploadFile($targetPath) $this->_createThumbnail($targetPath . '/' . $uploader->getUploadedFileName()); - $result['cookie'] = [ - 'name' => $this->_helper->getSession()->getName(), - 'value' => $this->_helper->getSession()->getSessionId(), - 'lifetime' => $this->_helper->getSession()->getCookieLifetime(), - 'path' => $this->_helper->getSession()->getCookiePath(), - 'domain' => $this->_helper->getSession()->getCookieDomain() - ]; - return $result; } diff --git a/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php b/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php index 0951f7b50f2..ddd6ebc43ce 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php @@ -152,8 +152,7 @@ public function testUploadFile() $this->_helperStorage->expects($this->any())->method('getSession')->will($this->returnValue($session)); $expectedResult = [ - 'not_empty', - 'cookie' => ['name' => null, 'value' => null, 'lifetime' => null, 'path' => null, 'domain' => null], + 'not_empty' ]; $this->assertEquals($expectedResult, $this->_storageModel->uploadFile($this->_storageRoot)); diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index a6984b449d9..b44691c0df9 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -46,6 +46,7 @@ Disallow: /*SID= </header> <footer translate="copyright"> <copyright>Copyright © 2013-present Magento, Inc. All rights reserved.</copyright> + <report_bugs>1</report_bugs> </footer> </design> <theme> diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index 5ff82ce2db6..148267feeaa 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -214,6 +214,10 @@ <item name="path" xsi:type="string">design/footer/absolute_footer</item> <item name="fieldset" xsi:type="string">other_settings/footer</item> </item> + <item name="footer_report_bugs" xsi:type="array"> + <item name="path" xsi:type="string">design/footer/report_bugs</item> + <item name="fieldset" xsi:type="string">other_settings/footer</item> + </item> <item name="default_robots" xsi:type="array"> <item name="path" xsi:type="string">design/search_engine_robots/default_robots</item> <item name="fieldset" xsi:type="string">other_settings/search_engine_robots</item> diff --git a/app/code/Magento/Theme/i18n/en_US.csv b/app/code/Magento/Theme/i18n/en_US.csv index db641b5da1f..c8c586f0bc6 100644 --- a/app/code/Magento/Theme/i18n/en_US.csv +++ b/app/code/Magento/Theme/i18n/en_US.csv @@ -188,3 +188,4 @@ Settings,Settings ID,ID View,View Action,Action +"Display Report Bugs Link","Display Report Bugs Link" diff --git a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml index 48adca3b1a1..8d4580f90c7 100644 --- a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml @@ -233,6 +233,20 @@ <dataScope>footer_copyright</dataScope> </settings> </field> + <field name="footer_report_bugs" formElement="select"> + <settings> + <dataType>text</dataType> + <label translate="true">Display Report Bugs Link</label> + <dataScope>footer_report_bugs</dataScope> + </settings> + <formElements> + <select> + <settings> + <options class="Magento\Config\Model\Config\Source\Yesno"/> + </settings> + </select> + </formElements> + </field> </fieldset> <fieldset name="search_engine_robots" sortOrder="120"> <settings> diff --git a/app/code/Magento/Theme/view/frontend/layout/default.xml b/app/code/Magento/Theme/view/frontend/layout/default.xml index 716341f5a64..c84222be19c 100644 --- a/app/code/Magento/Theme/view/frontend/layout/default.xml +++ b/app/code/Magento/Theme/view/frontend/layout/default.xml @@ -119,7 +119,7 @@ </arguments> </block> <block class="Magento\Theme\Block\Html\Footer" name="copyright" template="Magento_Theme::html/copyright.phtml"/> - <block class="Magento\Framework\View\Element\Template" name="report.bugs" template="Magento_Theme::html/bugreport.phtml" /> + <block class="Magento\Framework\View\Element\Template" name="report.bugs" template="Magento_Theme::html/bugreport.phtml" ifconfig="design/footer/report_bugs"/> </container> </referenceContainer> <referenceContainer name="before.body.end"> diff --git a/app/code/Magento/ThemeGraphQl/README.md b/app/code/Magento/ThemeGraphQl/README.md new file mode 100644 index 00000000000..fed6b54fa5c --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/README.md @@ -0,0 +1,4 @@ +# ThemeGraphQlhQl + +**ThemeGraphQlhQl** provides type information for the GraphQl module +to generate theme fields information endpoints. diff --git a/app/code/Magento/ThemeGraphQl/composer.json b/app/code/Magento/ThemeGraphQl/composer.json new file mode 100644 index 00000000000..e3aac55aea3 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-theme-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*" + }, + "suggest": { + "magento/module-store-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\ThemeGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/ThemeGraphQl/etc/module.xml b/app/code/Magento/ThemeGraphQl/etc/module.xml new file mode 100644 index 00000000000..0e10b776af9 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_ThemeGraphQl"/> +</config> diff --git a/app/code/Magento/ThemeGraphQl/etc/schema.graphqls b/app/code/Magento/ThemeGraphQl/etc/schema.graphqls new file mode 100644 index 00000000000..325fcc8bd98 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/etc/schema.graphqls @@ -0,0 +1,19 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. +type StoreConfig @doc(description: "The type contains information about a store config") { + head_shortcut_icon : String @doc(description: "Favicon Icon") + default_title : String @doc(description: "Default Page Title") + title_prefix : String @doc(description: "Page Title Prefix") + title_suffix : String @doc(description: "Page Title Suffix") + default_description : String @doc(description: "Default Meta Description") + default_keywords : String @doc(description: "Default Meta Keywords") + head_includes : String @doc(description: "Scripts and Style Sheets") + demonotice : Int @doc(description: "Display Demo Store Notice") + header_logo_src : String @doc(description: "Logo Image") + logo_width : Int @doc(description: "Logo Attribute Width") + logo_height : Int @doc(description: "Logo Attribute Height") + welcome : String @doc(description: "Welcome Text") + logo_alt : String @doc(description: "Logo Image Alt") + absolute_footer : String @doc(description: "Footer Miscellaneous HTML") + copyright : String @doc(description: "Copyright") +} diff --git a/app/code/Magento/ThemeGraphQl/registration.php b/app/code/Magento/ThemeGraphQl/registration.php new file mode 100644 index 00000000000..e320fbc9868 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/registration.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_ThemeGraphQl', __DIR__); diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Bookmark/Save.php b/app/code/Magento/Ui/Controller/Adminhtml/Bookmark/Save.php index 60d374fbf26..b45880c1ce7 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Bookmark/Save.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Bookmark/Save.php @@ -5,6 +5,7 @@ */ namespace Magento\Ui\Controller\Adminhtml\Bookmark; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Authorization\Model\UserContextInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\Json\DecoderInterface; @@ -20,7 +21,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends AbstractAction +class Save extends AbstractAction implements HttpPostActionInterface { /** * Identifier for current bookmark diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php index fb99cef8e53..b983e56b8ae 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php @@ -14,6 +14,11 @@ use Magento\Framework\Escaper; use Magento\Framework\Controller\Result\JsonFactory; +/** + * Render a component. + * + * @SuppressWarnings(PHPMD.AllPurposeAction) + */ class Render extends AbstractAction { /** diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render/Handle.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render/Handle.php index cc028a45545..d1254790cb8 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render/Handle.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render/Handle.php @@ -5,6 +5,7 @@ */ namespace Magento\Ui\Controller\Adminhtml\Index\Render; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\View\Element\Template; use Magento\Ui\Component\Control\ActionPool; use Magento\Ui\Component\Wrapper\UiComponent; @@ -13,7 +14,7 @@ /** * Class Handle */ -class Handle extends AbstractAction +class Handle extends AbstractAction implements HttpGetActionInterface { /** * Render UI component by namespace in handle context diff --git a/app/code/Magento/Ui/DataProvider/Modifier/Pool.php b/app/code/Magento/Ui/DataProvider/Modifier/Pool.php index 34a24499834..882f97d57e9 100644 --- a/app/code/Magento/Ui/DataProvider/Modifier/Pool.php +++ b/app/code/Magento/Ui/DataProvider/Modifier/Pool.php @@ -88,14 +88,7 @@ public function getModifiersInstances() protected function sort(array $data) { usort($data, function (array $a, array $b) { - $a['sortOrder'] = $this->getSortOrder($a); - $b['sortOrder'] = $this->getSortOrder($b); - - if ($a['sortOrder'] == $b['sortOrder']) { - return 0; - } - - return ($a['sortOrder'] < $b['sortOrder']) ? -1 : 1; + return $this->getSortOrder($a) <=> $this->getSortOrder($b); }); return $data; diff --git a/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js b/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js index fe312738469..ac1de4631e9 100644 --- a/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js +++ b/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js @@ -224,7 +224,7 @@ define([ */ build: function (parent, node, name) { var defaults = parent && parent.childDefaults || {}, - children = node.children, + children = this.filterDisabledChildren(node.children), type = getNodeType(parent, node), dataScope = getDataScope(parent, node), component, @@ -294,6 +294,35 @@ define([ return node; }, + /** + * Filter out all disabled components. + * + * @param {Object} children + * @returns {*} + */ + filterDisabledChildren: function (children) { + var cIds; + + //cleanup children config.componentDisabled = true + if (children && typeof children === 'object') { + cIds = Object.keys(children); + + if (cIds) { + _.each(cIds, function (cId) { + if (typeof children[cId] === 'object' && + children[cId].hasOwnProperty('config') && + typeof children[cId].config === 'object' && + children[cId].config.hasOwnProperty('componentDisabled') && + children[cId].config.componentDisabled === true) { + delete children[cId]; + } + }); + } + } + + return children; + }, + /** * Init component. * 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 be7a1a13fbd..c75f7797cf0 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 @@ -11,8 +11,9 @@ define([ 'mageUtils', 'uiRegistry', './column', - 'Magento_Ui/js/modal/confirm' -], function (_, utils, registry, Column, confirm) { + 'Magento_Ui/js/modal/confirm', + 'mage/dataPost' +], function (_, utils, registry, Column, confirm, dataPost) { 'use strict'; return Column.extend({ @@ -267,7 +268,14 @@ define([ * @param {Object} action - Action's data. */ defaultCallback: function (actionIndex, recordId, action) { - window.location.href = action.href; + if (action.post) { + dataPost().postData({ + action: action.href, + data: {} + }); + } else { + window.location.href = action.href; + } }, /** 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 4590af4f2d6..03012918f4a 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 @@ -12,7 +12,8 @@ define([ var counter = 1, watchers, - globalObserver; + globalObserver, + disabledNodes = []; watchers = { selectors: {}, @@ -238,11 +239,58 @@ define([ }; } + /** + * Verify if the DOM node is a child of a defined disabled node, if so we shouldn't observe provided mutation. + * + * @param {Object} mutation - a single mutation + * @returns {Boolean} + */ + function shouldObserveMutation(mutation) { + var isDisabled; + + if (disabledNodes.length > 0) { + // Iterate through the disabled nodes and determine if this mutation is occurring inside one of them + isDisabled = _.find(disabledNodes, function (node) { + return node === mutation.target || $.contains(node, mutation.target); + }); + + // If we find a matching node we should not observe the mutation + return !isDisabled; + } + + return true; + } + + /** + * Should we observe these mutations? Check the first and last mutation to determine if this is a disabled mutation, + * we check both the first and last in case one has been removed from the DOM during the process of the mutation. + * + * @param {Array} mutations - An array of mutation records. + * @returns {Boolean} + */ + function shouldObserveMutations(mutations) { + var firstMutation, + lastMutation; + + if (mutations.length > 0) { + firstMutation = mutations[0]; + lastMutation = mutations[mutations.length - 1]; + + return shouldObserveMutation(firstMutation) && shouldObserveMutation(lastMutation); + } + + return true; + } + globalObserver = new MutationObserver(function (mutations) { - var changes = formChangesLists(mutations); + var changes; + + if (shouldObserveMutations(mutations)) { + changes = formChangesLists(mutations); - changes.removed.forEach(processRemoved); - changes.added.forEach(processAdded); + changes.removed.forEach(processRemoved); + changes.added.forEach(processAdded); + } }); globalObserver.observe(document.body, { @@ -251,6 +299,16 @@ define([ }); return { + /** + * Disable a node from being observed by the mutations, you may want to disable specific aspects of the + * application which are heavy on DOM changes. The observer running on some actions could cause significant + * delays and degrade the performance of that specific part of the application exponentially. + * + * @param {HTMLElement} node - a HTML node within the document + */ + disableNode: function (node) { + disabledNodes.push(node); + }, /** * Adds listener for the appearance of nodes that matches provided diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal.js b/app/code/Magento/Ui/view/base/web/js/modal/modal.js index f016b0dbefd..c81274337f4 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal.js @@ -363,14 +363,7 @@ define([ this.modal.data('active', false); if (this.overlay) { - // In cases when one modal is closed but there is another modal open (e.g. admin notifications) - // to avoid collisions between overlay and modal zIndexes - // overlay zIndex is set to be less than modal one - if (this._getVisibleCount() === 1) { - this.overlay.zIndex(this.prevOverlayIndex - 1); - } else { - this.overlay.zIndex(this.prevOverlayIndex); - } + this.overlay.zIndex(this.prevOverlayIndex - 1); } }, diff --git a/app/code/Magento/UrlRewrite/Block/Edit.php b/app/code/Magento/UrlRewrite/Block/Edit.php index 115c5db43a7..c8716f6c80f 100644 --- a/app/code/Magento/UrlRewrite/Block/Edit.php +++ b/app/code/Magento/UrlRewrite/Block/Edit.php @@ -173,7 +173,7 @@ protected function _addDeleteButton() ['id' => $this->getUrlRewrite()->getId()] ) ) - . ')', + . ', {data: {}})', 'class' => 'scalable delete', 'level' => -1 ] @@ -243,7 +243,7 @@ private function _getSelectorBlock() * Since buttons are set as children, we remove them as children after generating them * not to duplicate them in future * - * @param string $area + * @param string|null $area * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/CmsPageGrid.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/CmsPageGrid.php index 480efa82357..7240dfc36f1 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/CmsPageGrid.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/CmsPageGrid.php @@ -6,7 +6,11 @@ */ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -class CmsPageGrid extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite as RewriteAction; + +class CmsPageGrid extends RewriteAction implements HttpPostActionInterface, HttpGetActionInterface { /** * Ajax CMS pages grid action diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Delete.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Delete.php index f8f7b145e28..dc49776a1ac 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Delete.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Delete.php @@ -6,7 +6,9 @@ */ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -class Delete extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite +use Magento\Framework\App\Action\HttpPostActionInterface; + +class Delete extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implements HttpPostActionInterface { /** * URL rewrite delete action diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Edit.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Edit.php index b7ff9221300..e2161f4ffd9 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Edit.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Edit.php @@ -6,7 +6,9 @@ */ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -class Edit extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Edit extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implements HttpGetActionInterface { /**#@+ * Modes diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Index.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Index.php index 066377ff7bd..4f3a1e7849e 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Index.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -class Index extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implements HttpGetActionInterface { /** * Show URL rewrites index page diff --git a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php index 6325fe162d9..c508e85d87c 100644 --- a/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php +++ b/app/code/Magento/UrlRewrite/Controller/Adminhtml/Url/Rewrite/Save.php @@ -7,11 +7,12 @@ namespace Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite +class Save extends \Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite implements HttpPostActionInterface { /** * @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator diff --git a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php new file mode 100644 index 00000000000..1c25ffd1e9f --- /dev/null +++ b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\UrlRewriteGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewriteGraphQl\Model\Resolver\UrlRewrite\CustomUrlLocatorInterface; + +/** + * UrlRewrite field resolver, used for GraphQL request processing. + */ +class EntityUrl implements ResolverInterface +{ + /** + * @var UrlFinderInterface + */ + private $urlFinder; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CustomUrlLocatorInterface + */ + private $customUrlLocator; + + /** + * @param UrlFinderInterface $urlFinder + * @param StoreManagerInterface $storeManager + * @param CustomUrlLocatorInterface $customUrlLocator + */ + public function __construct( + UrlFinderInterface $urlFinder, + StoreManagerInterface $storeManager, + CustomUrlLocatorInterface $customUrlLocator + ) { + $this->urlFinder = $urlFinder; + $this->storeManager = $storeManager; + $this->customUrlLocator = $customUrlLocator; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['url']) || empty(trim($args['url']))) { + throw new GraphQlInputException(__('"url" argument should be specified and not empty')); + } + + $result = null; + $url = $args['url']; + if (substr($url, 0, 1) === '/' && $url !== '/') { + $url = ltrim($url, '/'); + } + $customUrl = $this->customUrlLocator->locateUrl($url); + $url = $customUrl ?: $url; + $urlRewrite = $this->findCanonicalUrl($url); + if ($urlRewrite) { + $result = [ + 'id' => $urlRewrite->getEntityId(), + 'canonical_url' => $urlRewrite->getTargetPath(), + 'type' => $this->sanitizeType($urlRewrite->getEntityType()) + ]; + } + return $result; + } + + /** + * Find the canonical url passing through all redirects if any + * + * @param string $requestPath + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + */ + private function findCanonicalUrl(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + { + $urlRewrite = $this->findUrlFromRequestPath($requestPath); + if ($urlRewrite && $urlRewrite->getRedirectType() > 0) { + while ($urlRewrite && $urlRewrite->getRedirectType() > 0) { + $urlRewrite = $this->findUrlFromRequestPath($urlRewrite->getTargetPath()); + } + } + if (!$urlRewrite) { + $urlRewrite = $this->findUrlFromTargetPath($requestPath); + } + + return $urlRewrite; + } + + /** + * Find a url from a request url on the current store + * + * @param string $requestPath + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + */ + private function findUrlFromRequestPath(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + { + return $this->urlFinder->findOneByData( + [ + 'request_path' => $requestPath, + 'store_id' => $this->storeManager->getStore()->getId() + ] + ); + } + + /** + * Find a url from a target url on the current store + * + * @param string $targetPath + * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + */ + private function findUrlFromTargetPath(string $targetPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + { + return $this->urlFinder->findOneByData( + [ + 'target_path' => $targetPath, + 'store_id' => $this->storeManager->getStore()->getId() + ] + ); + } + + /** + * Sanitize the type to fit schema specifications + * + * @param string $type + * @return string + */ + private function sanitizeType(string $type) : string + { + return strtoupper(str_replace('-', '_', $type)); + } +} diff --git a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php index 488f1281ce3..0c4c78b5829 100644 --- a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php +++ b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/UrlRewrite.php @@ -11,12 +11,12 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\Model\AbstractModel; use Magento\UrlRewrite\Model\UrlFinderInterface; -use Magento\UrlRewriteGraphQl\Model\Resolver\UrlRewrite\CustomUrlLocatorInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteDTO; /** - * UrlRewrite field resolver, used for GraphQL request processing. + * Returns URL rewrites list for the specified product */ class UrlRewrite implements ResolverInterface { @@ -25,29 +25,13 @@ class UrlRewrite implements ResolverInterface */ private $urlFinder; - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var CustomUrlLocatorInterface - */ - private $customUrlLocator; - /** * @param UrlFinderInterface $urlFinder - * @param StoreManagerInterface $storeManager - * @param CustomUrlLocatorInterface $customUrlLocator */ public function __construct( - UrlFinderInterface $urlFinder, - StoreManagerInterface $storeManager, - CustomUrlLocatorInterface $customUrlLocator + UrlFinderInterface $urlFinder ) { $this->urlFinder = $urlFinder; - $this->storeManager = $storeManager; - $this->customUrlLocator = $customUrlLocator; } /** @@ -59,90 +43,51 @@ public function resolve( ResolveInfo $info, array $value = null, array $args = null - ) { - if (!isset($args['url']) || empty(trim($args['url']))) { - throw new GraphQlInputException(__('"url" argument should be specified and not empty')); + ): array { + if (!isset($value['model'])) { + throw new GraphQlInputException(__('"model" value should be specified')); } - $result = null; - $url = $args['url']; - if (substr($url, 0, 1) === '/' && $url !== '/') { - $url = ltrim($url, '/'); - } - $customUrl = $this->customUrlLocator->locateUrl($url); - $url = $customUrl ?: $url; - $urlRewrite = $this->findCanonicalUrl($url); - if ($urlRewrite) { - $result = [ - 'id' => $urlRewrite->getEntityId(), - 'canonical_url' => $urlRewrite->getTargetPath(), - 'type' => $this->sanitizeType($urlRewrite->getEntityType()) - ]; - } - return $result; - } + /** @var AbstractModel $entity */ + $entity = $value['model']; + $entityId = $entity->getEntityId(); - /** - * Find the canonical url passing through all redirects if any - * - * @param string $requestPath - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null - */ - private function findCanonicalUrl(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite - { - $urlRewrite = $this->findUrlFromRequestPath($requestPath); - if ($urlRewrite && $urlRewrite->getRedirectType() > 0) { - while ($urlRewrite && $urlRewrite->getRedirectType() > 0) { - $urlRewrite = $this->findUrlFromRequestPath($urlRewrite->getTargetPath()); + $urlRewriteCollection = $this->urlFinder->findAllByData([UrlRewriteDTO::ENTITY_ID => $entityId]); + $urlRewrites = []; + + /** @var UrlRewriteDTO $urlRewrite */ + foreach ($urlRewriteCollection as $urlRewrite) { + if ($urlRewrite->getRedirectType() !== 0) { + continue; } + + $urlRewrites[] = [ + 'url' => $urlRewrite->getRequestPath(), + 'parameters' => $this->getUrlParameters($urlRewrite->getTargetPath()) + ]; } - if (!$urlRewrite) { - $urlRewrite = $this->findUrlFromTargetPath($requestPath); - } - - return $urlRewrite; - } - /** - * Find a url from a request url on the current store - * - * @param string $requestPath - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null - */ - private function findUrlFromRequestPath(string $requestPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite - { - return $this->urlFinder->findOneByData( - [ - 'request_path' => $requestPath, - 'store_id' => $this->storeManager->getStore()->getId() - ] - ); + return $urlRewrites; } /** - * Find a url from a target url on the current store + * Parses target path and extracts parameters * * @param string $targetPath - * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null + * @return array */ - private function findUrlFromTargetPath(string $targetPath) : ?\Magento\UrlRewrite\Service\V1\Data\UrlRewrite + private function getUrlParameters(string $targetPath): array { - return $this->urlFinder->findOneByData( - [ - 'target_path' => $targetPath, - 'store_id' => $this->storeManager->getStore()->getId() - ] - ); - } + $urlParameters = []; + $targetPathParts = explode('/', trim($targetPath, '/')); - /** - * Sanitize the type to fit schema specifications - * - * @param string $type - * @return string - */ - private function sanitizeType(string $type) : string - { - return strtoupper(str_replace('-', '_', $type)); + for ($i = 3; ($i < sizeof($targetPathParts) - 1); $i += 2) { + $urlParameters[] = [ + 'name' => $targetPathParts[$i], + 'value' => $targetPathParts[$i + 1] + ]; + } + + return $urlParameters; } } diff --git a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls index 38f1d9c6563..dae695c69a3 100644 --- a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls @@ -8,8 +8,18 @@ type EntityUrl @doc(description: "EntityUrl is an output object containing the ` } type Query { - urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") @doc(description: "The urlResolver query returns the canonical URL for a specified product, category or CMS page") + urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page") } enum UrlRewriteEntityTypeEnum { } + +type UrlRewrite @doc(description: "The object contains URL rewrite details") { + url: String @doc(description: "Request URL") + parameters: [HttpQueryParameter] @doc(description: "Request parameters") +} + +type HttpQueryParameter @doc(description: "The object details of target path parameters") { + name: String @doc(description: "Parameter name") + value: String @doc(description: "Parameter value") +} diff --git a/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php b/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php index c0e78fae2c2..565b312325d 100644 --- a/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php +++ b/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php @@ -6,8 +6,10 @@ */ namespace Magento\User\Controller\Adminhtml\Auth; -use Magento\Security\Model\SecurityManager; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\ObjectManager; +use Magento\Security\Model\SecurityManager; use Magento\Backend\App\Action\Context; use Magento\User\Model\UserFactory; use Magento\User\Model\ResourceModel\User\CollectionFactory; @@ -16,14 +18,25 @@ use Magento\Framework\Exception\SecurityViolationException; use Magento\User\Controller\Adminhtml\Auth; use Magento\Backend\Helper\Data; +use Magento\User\Model\Spi\NotificatorInterface; -class Forgotpassword extends Auth +/** + * Initiate forgot-password process. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Forgotpassword extends Auth implements HttpGetActionInterface, HttpPostActionInterface { /** * @var SecurityManager */ protected $securityManager; + /** + * @var NotificatorInterface + */ + private $notificator; + /** * User model factory * @@ -41,13 +54,16 @@ class Forgotpassword extends Auth * @param UserFactory $userFactory * @param SecurityManager $securityManager * @param CollectionFactory $userCollectionFactory + * @param Data $backendDataHelper + * @param NotificatorInterface|null $notificator */ public function __construct( Context $context, UserFactory $userFactory, SecurityManager $securityManager, CollectionFactory $userCollectionFactory = null, - Data $backendDataHelper = null + Data $backendDataHelper = null, + ?NotificatorInterface $notificator = null ) { parent::__construct($context, $userFactory); $this->securityManager = $securityManager; @@ -55,6 +71,8 @@ public function __construct( ObjectManager::getInstance()->get(CollectionFactory::class); $this->backendDataHelper = $backendDataHelper ?: ObjectManager::getInstance()->get(Data::class); + $this->notificator = $notificator + ?? ObjectManager::getInstance()->get(NotificatorInterface::class); } /** @@ -96,7 +114,7 @@ public function execute() $newPassResetToken = $this->backendDataHelper->generateResetPasswordLinkToken(); $user->changeResetPasswordLinkToken($newPassResetToken); $user->save(); - $user->sendPasswordResetConfirmationEmail(); + $this->notificator->sendForgotPassword($user); } break; } diff --git a/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php b/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php index 7c0e31076ca..e3e1e3def39 100644 --- a/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php +++ b/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php @@ -6,10 +6,12 @@ */ namespace Magento\User\Controller\Adminhtml\Locks; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + /** * Locks Index action */ -class Index extends \Magento\User\Controller\Adminhtml\Locks +class Index extends \Magento\User\Controller\Adminhtml\Locks implements HttpGetActionInterface { /** * Render page with grid diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Index.php b/app/code/Magento/User/Controller/Adminhtml/User/Index.php index 7f79dd47737..bc4157c144a 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Index.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\User\Controller\Adminhtml\User; -class Index extends \Magento\User\Controller\Adminhtml\User +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\User\Controller\Adminhtml\User implements HttpGetActionInterface { /** * @return void diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Role/Index.php b/app/code/Magento/User/Controller/Adminhtml/User/Role/Index.php index e1853be17a1..8d468d45f1a 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Role/Index.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Role/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\User\Controller\Adminhtml\User\Role; -class Index extends \Magento\User\Controller\Adminhtml\User\Role +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\User\Controller\Adminhtml\User\Role implements HttpGetActionInterface { /** * Show grid with roles existing in systems diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Role/RoleGrid.php b/app/code/Magento/User/Controller/Adminhtml/User/Role/RoleGrid.php index 4d97b62cd8b..ad8c87b617d 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Role/RoleGrid.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Role/RoleGrid.php @@ -6,7 +6,11 @@ */ namespace Magento\User\Controller\Adminhtml\User\Role; -class RoleGrid extends \Magento\User\Controller\Adminhtml\User\Role +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\User\Controller\Adminhtml\User\Role as RoleAction; + +class RoleGrid extends RoleAction implements HttpGetActionInterface, HttpPostActionInterface { /** * Action for ajax request from grid diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php b/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php index 9dfe34e4353..44862f1fce2 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Role/SaveRole.php @@ -7,6 +7,7 @@ namespace Magento\User\Controller\Adminhtml\User\Role; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Authorization\Model\Acl\Role\Group as RoleGroup; use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\Controller\ResultFactory; @@ -16,7 +17,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class SaveRole extends \Magento\User\Controller\Adminhtml\User\Role +class SaveRole extends \Magento\User\Controller\Adminhtml\User\Role implements HttpPostActionInterface { /** * Session keys for Info form data diff --git a/app/code/Magento/User/Controller/Adminhtml/User/RoleGrid.php b/app/code/Magento/User/Controller/Adminhtml/User/RoleGrid.php index 2bfbadd3a0d..e171f0a8c2b 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/RoleGrid.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/RoleGrid.php @@ -6,7 +6,11 @@ */ namespace Magento\User\Controller\Adminhtml\User; -class RoleGrid extends \Magento\User\Controller\Adminhtml\User +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\User\Controller\Adminhtml\User as UserAction; + +class RoleGrid extends UserAction implements HttpGetActionInterface, HttpPostActionInterface { /** * @return void diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Save.php b/app/code/Magento/User/Controller/Adminhtml/User/Save.php index 4b984b761c1..521c09f7b77 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Save.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Save.php @@ -6,14 +6,18 @@ namespace Magento\User\Controller\Adminhtml\User; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\State\UserLockedException; use Magento\Security\Model\SecurityCookie; +use Magento\User\Model\Spi\NotificationExceptionInterface; /** + * Save admin user. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Save extends \Magento\User\Controller\Adminhtml\User +class Save extends \Magento\User\Controller\Adminhtml\User implements HttpPostActionInterface { /** * @var SecurityCookie @@ -36,7 +40,7 @@ private function getSecurityCookie() } /** - * @return void + * @inheritDoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -44,10 +48,14 @@ public function execute() { $userId = (int)$this->getRequest()->getParam('user_id'); $data = $this->getRequest()->getPostValue(); + if (array_key_exists('form_key', $data)) { + unset($data['form_key']); + } if (!$data) { $this->_redirect('adminhtml/*/'); return; } + /** @var $model \Magento\User\Model\User */ $model = $this->_userFactory->create()->load($userId); if ($userId && $model->isObjectNew()) { @@ -87,17 +95,19 @@ public function execute() $currentUser->performIdentityCheck($data[$currentUserPasswordField]); $model->save(); - $model->sendNotificationEmailsIfRequired(); - $this->messageManager->addSuccess(__('You saved the user.')); $this->_getSession()->setUserData(false); $this->_redirect('adminhtml/*/'); + + $model->sendNotificationEmailsIfRequired(); } catch (UserLockedException $e) { $this->_auth->logout(); $this->getSecurityCookie()->setLogoutReasonCookie( \Magento\Security\Model\AdminSessionsManager::LOGOUT_REASON_USER_LOCKED ); $this->_redirect('adminhtml/*/'); + } catch (NotificationExceptionInterface $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } catch (\Magento\Framework\Exception\AuthenticationException $e) { $this->messageManager->addError( __('The password entered for the current user is invalid. Verify the password and try again.') @@ -116,6 +126,8 @@ public function execute() } /** + * Redirect to Edit form. + * * @param \Magento\User\Model\User $model * @param array $data * @return void diff --git a/app/code/Magento/User/Model/Notificator.php b/app/code/Magento/User/Model/Notificator.php new file mode 100644 index 00000000000..3a5522db4c5 --- /dev/null +++ b/app/code/Magento/User/Model/Notificator.php @@ -0,0 +1,194 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\MailException; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\Spi\NotificatorInterface; +use Magento\Backend\App\ConfigInterface; +use Magento\Framework\Mail\Template\TransportBuilder; +use Magento\Framework\App\DeploymentConfig; +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Email\Model\BackendTemplate; + +/** + * @inheritDoc + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Notificator implements NotificatorInterface +{ + /** + * @var TransportBuilder + */ + private $transportBuilder; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var DeploymentConfig + */ + private $deployConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param TransportBuilder $transportBuilder + * @param ConfigInterface $config + * @param DeploymentConfig $deployConfig + * @param StoreManagerInterface $storeManager + */ + public function __construct( + TransportBuilder $transportBuilder, + ConfigInterface $config, + DeploymentConfig $deployConfig, + StoreManagerInterface $storeManager + ) { + $this->transportBuilder = $transportBuilder; + $this->config = $config; + $this->deployConfig = $deployConfig; + $this->storeManager = $storeManager; + } + + /** + * Send a notification. + * + * @param string $templateConfigId + * @param array $templateVars + * @param string $toEmail + * @param string $toName + * @throws MailException + * + * @return void + */ + private function sendNotification( + string $templateConfigId, + array $templateVars, + string $toEmail, + string $toName + ): void { + $transport = $this->transportBuilder + ->setTemplateIdentifier($this->config->getValue($templateConfigId)) + ->setTemplateModel(BackendTemplate::class) + ->setTemplateOptions([ + 'area' => FrontNameResolver::AREA_CODE, + 'store' => Store::DEFAULT_STORE_ID + ]) + ->setTemplateVars($templateVars) + ->setFrom( + $this->config->getValue('admin/emails/forgot_email_identity') + ) + ->addTo($toEmail, $toName) + ->getTransport(); + $transport->sendMessage(); + } + + /** + * @inheritDoc + */ + public function sendForgotPassword(UserInterface $user): void + { + try { + $this->sendNotification( + 'admin/emails/forgot_email_template', + [ + 'user' => $user, + 'store' => $this->storeManager->getStore( + Store::DEFAULT_STORE_ID + ) + ], + $user->getEmail(), + $user->getFirstName().' '.$user->getLastName() + ); + } catch (LocalizedException $exception) { + throw new NotificatorException( + __($exception->getMessage()), + $exception + ); + } + } + + /** + * @inheritDoc + */ + public function sendCreated(UserInterface $user): void + { + $toEmails = []; + $generalEmail = $this->config->getValue( + 'trans_email/ident_general/email' + ); + if ($generalEmail) { + $toEmails[] = $generalEmail; + } + if ($adminEmail = $this->deployConfig->get('user_admin_email')) { + $toEmails[] = $adminEmail; + } + + try { + foreach ($toEmails as $toEmail) { + $this->sendNotification( + 'admin/emails/new_user_notification_template', + [ + 'user' => $user, + 'store' => $this->storeManager->getStore( + Store::DEFAULT_STORE_ID + ) + ], + $toEmail, + __('Administrator')->getText() + ); + } + } catch (LocalizedException $exception) { + throw new NotificatorException( + __($exception->getMessage()), + $exception + ); + } + } + + /** + * @inheritDoc + */ + public function sendUpdated(UserInterface $user, array $changed): void + { + $email = $user->getEmail(); + if ($user instanceof User) { + $email = $user->getOrigData('email'); + } + + try { + $this->sendNotification( + 'admin/emails/user_notification_template', + [ + 'user' => $user, + 'store' => $this->storeManager->getStore( + Store::DEFAULT_STORE_ID + ), + 'changes' => implode(', ', $changed) + ], + $email, + $user->getFirstName().' '.$user->getLastName() + ); + } catch (LocalizedException $exception) { + throw new NotificatorException( + __($exception->getMessage()), + $exception + ); + } + } +} diff --git a/app/code/Magento/User/Model/NotificatorException.php b/app/code/Magento/User/Model/NotificatorException.php new file mode 100644 index 00000000000..5b581879814 --- /dev/null +++ b/app/code/Magento/User/Model/NotificatorException.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model; + +use Magento\Framework\Exception\MailException; +use Magento\User\Model\Spi\NotificationExceptionInterface; + +/** + * When notificator cannot send an email. + */ +class NotificatorException extends MailException implements NotificationExceptionInterface +{ + +} diff --git a/app/code/Magento/User/Model/Spi/NotificationExceptionInterface.php b/app/code/Magento/User/Model/Spi/NotificationExceptionInterface.php new file mode 100644 index 00000000000..47af7cbe6dd --- /dev/null +++ b/app/code/Magento/User/Model/Spi/NotificationExceptionInterface.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model\Spi; + +/** + * When a notification cannot be sent. + */ +interface NotificationExceptionInterface extends \Throwable +{ + +} diff --git a/app/code/Magento/User/Model/Spi/NotificatorInterface.php b/app/code/Magento/User/Model/Spi/NotificatorInterface.php new file mode 100644 index 00000000000..f5e8ee68e7e --- /dev/null +++ b/app/code/Magento/User/Model/Spi/NotificatorInterface.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model\Spi; + +use Magento\User\Api\Data\UserInterface; + +/** + * Use to send out notifications about user related events. + */ +interface NotificatorInterface +{ + /** + * Send notification when a user requests password reset. + * + * @param UserInterface $user User that requested password reset. + * @throws NotificationExceptionInterface + * + * @return void + */ + public function sendForgotPassword(UserInterface $user): void; + + /** + * Send a notification when a new user is created. + * + * @param UserInterface $user The new user. + * @throws NotificationExceptionInterface + * + * @return void + */ + public function sendCreated(UserInterface $user): void; + + /** + * Send a notification when a user is updated. + * + * @param UserInterface $user The user updated. + * @param string[] $changed List of changed properties. + * @throws NotificationExceptionInterface + * + * @return void + */ + public function sendUpdated(UserInterface $user, array $changed): void; +} diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 29618c981a2..4050d79e593 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -6,14 +6,15 @@ namespace Magento\User\Model; -use Magento\Backend\App\Area\FrontNameResolver; use Magento\Backend\Model\Auth\Credential\StorageInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Store\Model\Store; use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\Spi\NotificationExceptionInterface; +use Magento\User\Model\Spi\NotificatorInterface; +use Magento\Framework\App\DeploymentConfig; /** * Admin user model @@ -37,12 +38,21 @@ class User extends AbstractModel implements StorageInterface, UserInterface { /** - * Configuration paths for email templates and identities + * @deprecated + * @see \Magento\User\Model\Spi\NotificatorInterface */ const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'admin/emails/forgot_email_template'; + /** + * @deprecated + * @see \Magento\User\Model\Spi\NotificatorInterface + */ const XML_PATH_FORGOT_EMAIL_IDENTITY = 'admin/emails/forgot_email_identity'; + /** + * @deprecated + * @see \Magento\User\Model\Spi\NotificatorInterface + */ const XML_PATH_USER_NOTIFICATION_TEMPLATE = 'admin/emails/user_notification_template'; /** @deprecated */ @@ -105,12 +115,12 @@ class User extends AbstractModel implements StorageInterface, UserInterface protected $_encryptor; /** - * @var \Magento\Framework\Mail\Template\TransportBuilder + * @deprecated */ protected $_transportBuilder; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @deprecated */ protected $_storeManager; @@ -124,6 +134,16 @@ class User extends AbstractModel implements StorageInterface, UserInterface */ private $serializer; + /** + * @var NotificatorInterface + */ + private $notificator; + + /** + * @deprecated + */ + private $deploymentConfig; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -139,6 +159,8 @@ class User extends AbstractModel implements StorageInterface, UserInterface * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data * @param Json $serializer + * @param DeploymentConfig|null $deploymentConfig + * @param NotificatorInterface|null $notificator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -155,7 +177,9 @@ public function __construct( \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - Json $serializer = null + Json $serializer = null, + DeploymentConfig $deploymentConfig = null, + ?NotificatorInterface $notificator = null ) { $this->_encryptor = $encryptor; parent::__construct($context, $registry, $resource, $resourceCollection, $data); @@ -166,7 +190,12 @@ public function __construct( $this->_transportBuilder = $transportBuilder; $this->_storeManager = $storeManager; $this->validationRules = $validationRules; - $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->serializer = $serializer + ?: ObjectManager::getInstance()->get(Json::class); + $this->deploymentConfig = $deploymentConfig + ?: ObjectManager::getInstance()->get(DeploymentConfig::class); + $this->notificator = $notificator + ?: ObjectManager::getInstance()->get(NotificatorInterface::class); } /** @@ -180,6 +209,8 @@ protected function _construct() } /** + * Removing dependencies and leaving only entity's properties. + * * @return string[] */ public function __sleep() @@ -196,12 +227,18 @@ public function __sleep() '_encryptor', '_transportBuilder', '_storeManager', - '_validatorBeforeSave' + '_validatorBeforeSave', + 'validationRules', + 'serializer', + 'deploymentConfig', + 'notificator' ] ); } /** + * Restoring required objects after serialization. + * * @return void */ public function __wakeup() @@ -218,6 +255,9 @@ public function __wakeup() $this->_encryptor = $objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class); $this->_transportBuilder = $objectManager->get(\Magento\Framework\Mail\Template\TransportBuilder::class); $this->_storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); + $this->validationRules = $objectManager->get(UserValidationRules::class); + $this->deploymentConfig = $objectManager->get(DeploymentConfig::class); + $this->notificator = $objectManager->get(NotificatorInterface::class); } /** @@ -388,7 +428,7 @@ public function deleteFromRole() } /** - * Check if such combination role/user exists + * Check if such combination role/user exists. * * @return bool */ @@ -399,28 +439,24 @@ public function roleUserExists() } /** - * Send email with reset password confirmation link + * Send email with reset password confirmation link. + * + * @deprecated + * @see NotificatorInterface::sendForgotPassword() * * @return $this */ public function sendPasswordResetConfirmationEmail() { - $templateId = $this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_TEMPLATE); - $transport = $this->_transportBuilder->setTemplateIdentifier($templateId) - ->setTemplateModel(\Magento\Email\Model\BackendTemplate::class) - ->setTemplateOptions(['area' => FrontNameResolver::AREA_CODE, 'store' => Store::DEFAULT_STORE_ID]) - ->setTemplateVars(['user' => $this, 'store' => $this->_storeManager->getStore(Store::DEFAULT_STORE_ID)]) - ->setFrom($this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_IDENTITY)) - ->addTo($this->getEmail(), $this->getName()) - ->getTransport(); + $this->notificator->sendForgotPassword($this); - $transport->sendMessage(); return $this; } /** * Send email to when password is resetting * + * @throws NotificationExceptionInterface * @return $this * @deprecated 100.1.0 */ @@ -431,20 +467,20 @@ public function sendPasswordResetNotificationEmail() } /** - * Check changes and send notification emails + * Check changes and send notification emails. * + * @throws NotificationExceptionInterface * @return $this * @since 100.1.0 */ public function sendNotificationEmailsIfRequired() { - $changes = $this->createChangesDescriptionString(); - - if ($changes) { - if ($this->getEmail() != $this->getOrigData('email') && $this->getOrigData('email')) { - $this->sendUserNotificationEmail($changes, $this->getOrigData('email')); - } - $this->sendUserNotificationEmail($changes); + if ($this->isObjectNew()) { + //Notification about a new user. + $this->notificator->sendCreated($this); + } elseif ($changes = $this->createChangesDescriptionString()) { + //User changed. + $this->notificator->sendUpdated($this, explode(', ', $changes)); } return $this; @@ -478,35 +514,21 @@ protected function createChangesDescriptionString() } /** - * Send user notification email + * Send user notification email. * * @param string $changes * @param string $email + * @throws NotificationExceptionInterface * @return $this * @since 100.1.0 + * @deprecated + * @see NotificatorInterface::sendUpdated() + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function sendUserNotificationEmail($changes, $email = null) { - if ($email === null) { - $email = $this->getEmail(); - } + $this->notificator->sendUpdated($this, explode(', ', $changes)); - $transport = $this->_transportBuilder - ->setTemplateIdentifier($this->_config->getValue(self::XML_PATH_USER_NOTIFICATION_TEMPLATE)) - ->setTemplateModel(\Magento\Email\Model\BackendTemplate::class) - ->setTemplateOptions(['area' => FrontNameResolver::AREA_CODE, 'store' => Store::DEFAULT_STORE_ID]) - ->setTemplateVars( - [ - 'user' => $this, - 'store' => $this->_storeManager->getStore(Store::DEFAULT_STORE_ID), - 'changes' => $changes - ] - ) - ->setFrom($this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_IDENTITY)) - ->addTo($email, $this->getName()) - ->getTransport(); - - $transport->sendMessage(); return $this; } @@ -738,7 +760,7 @@ public function setHasAvailableResources($hasResources) } /** - * {@inheritdoc} + * @inheritDoc */ public function getFirstName() { @@ -746,7 +768,7 @@ public function getFirstName() } /** - * {@inheritdoc} + * @inheritDoc */ public function setFirstName($firstName) { @@ -754,7 +776,7 @@ public function setFirstName($firstName) } /** - * {@inheritdoc} + * @inheritDoc */ public function getLastName() { @@ -762,7 +784,7 @@ public function getLastName() } /** - * {@inheritdoc} + * @inheritDoc */ public function setLastName($lastName) { @@ -770,7 +792,7 @@ public function setLastName($lastName) } /** - * {@inheritdoc} + * @inheritDoc */ public function getEmail() { @@ -778,7 +800,7 @@ public function getEmail() } /** - * {@inheritdoc} + * @inheritDoc */ public function setEmail($email) { @@ -786,7 +808,7 @@ public function setEmail($email) } /** - * {@inheritdoc} + * @inheritDoc */ public function getUserName() { @@ -794,7 +816,7 @@ public function getUserName() } /** - * {@inheritdoc} + * @inheritDoc */ public function setUserName($userName) { @@ -802,7 +824,7 @@ public function setUserName($userName) } /** - * {@inheritdoc} + * @inheritDoc */ public function getPassword() { @@ -810,7 +832,7 @@ public function getPassword() } /** - * {@inheritdoc} + * @inheritDoc */ public function setPassword($password) { @@ -818,7 +840,7 @@ public function setPassword($password) } /** - * {@inheritdoc} + * @inheritDoc */ public function getCreated() { @@ -826,7 +848,7 @@ public function getCreated() } /** - * {@inheritdoc} + * @inheritDoc */ public function setCreated($created) { @@ -834,7 +856,7 @@ public function setCreated($created) } /** - * {@inheritdoc} + * @inheritDoc */ public function getModified() { @@ -842,7 +864,7 @@ public function getModified() } /** - * {@inheritdoc} + * @inheritDoc */ public function setModified($modified) { @@ -850,7 +872,7 @@ public function setModified($modified) } /** - * {@inheritdoc} + * @inheritDoc */ public function getIsActive() { @@ -858,7 +880,7 @@ public function getIsActive() } /** - * {@inheritdoc} + * @inheritDoc */ public function setIsActive($isActive) { @@ -866,7 +888,7 @@ public function setIsActive($isActive) } /** - * {@inheritdoc} + * @inheritDoc */ public function getInterfaceLocale() { @@ -874,7 +896,7 @@ public function getInterfaceLocale() } /** - * {@inheritdoc} + * @inheritDoc */ public function setInterfaceLocale($interfaceLocale) { diff --git a/app/code/Magento/User/Test/Unit/Model/UserTest.php b/app/code/Magento/User/Test/Unit/Model/UserTest.php index 4bc5db138c7..670316c2500 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserTest.php @@ -6,7 +6,9 @@ namespace Magento\User\Test\Unit\Model; -use Magento\Framework\Serialize\Serializer\Json; +use Magento\User\Helper\Data as UserHelper; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\User\Model\User; /** * Test class for \Magento\User\Model\User testing @@ -16,55 +18,11 @@ */ class UserTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\User\Model\User */ - protected $model; + /** @var User */ + private $model; - /** @var \Magento\User\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ - protected $userDataMock; - - /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportBuilderMock; - - /** @var \Magento\Framework\Model\Context|\PHPUnit_Framework_MockObject_MockObject */ - protected $contextMock; - - /** @var \Magento\User\Model\ResourceModel\User|\PHPUnit_Framework_MockObject_MockObject */ - protected $resourceMock; - - /** @var \Magento\Framework\Data\Collection\AbstractDb|\PHPUnit_Framework_MockObject_MockObject */ - protected $collectionMock; - - /** @var \Magento\Framework\Mail\TransportInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportMock; - - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManagerMock; - - /** @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeMock; - - /** @var \Magento\Backend\App\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $configMock; - - /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $encryptorMock; - - /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $eventManagerMock; - - /** @var \Magento\Framework\Validator\DataObjectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $validatorObjectFactoryMock; - - /** @var \Magento\User\Model\UserValidationRules|\PHPUnit_Framework_MockObject_MockObject */ - protected $validationRulesMock; - - /** @var \Magento\Authorization\Model\RoleFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $roleFactoryMock; - - /** - * @var Json|\PHPUnit_Framework_MockObject_MockObject - */ - private $serializer; + /** @var UserHelper|\PHPUnit_Framework_MockObject_MockObject */ + private $userDataMock; /** * Set required values @@ -72,308 +30,20 @@ class UserTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->userDataMock = $this->getMockBuilder(\Magento\User\Helper\Data::class) + $this->userDataMock = $this->getMockBuilder(UserHelper::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $this->contextMock = $this->getMockBuilder(\Magento\Framework\Model\Context::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->resourceMock = $this->getMockBuilder(\Magento\User\Model\ResourceModel\User::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection\AbstractDb::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMockForAbstractClass(); - $coreRegistry = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['dispatch']) - ->getMockForAbstractClass(); - $this->validatorObjectFactoryMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObjectFactory::class) - ->disableOriginalConstructor()->setMethods(['create']) - ->getMock(); - $this->roleFactoryMock = $this->getMockBuilder(\Magento\Authorization\Model\RoleFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->transportMock = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->transportBuilderMock = $this->getMockBuilder(\Magento\Framework\Mail\Template\TransportBuilder::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->configMock = $this->getMockBuilder(\Magento\Backend\App\ConfigInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->validationRulesMock = $this->getMockBuilder(\Magento\User\Model\UserValidationRules::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->encryptorMock = $this->getMockBuilder(\Magento\Framework\Encryption\EncryptorInterface::class) - ->setMethods(['validateHash']) - ->getMockForAbstractClass(); - - $this->serializer = $this->createPartialMock(Json::class, ['serialize', 'unserialize']); - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectManagerHelper = new ObjectManager($this); $this->model = $objectManagerHelper->getObject( - \Magento\User\Model\User::class, + User::class, [ - 'eventManager' => $this->eventManagerMock, 'userData' => $this->userDataMock, - 'registry' => $coreRegistry, - 'resource' => $this->resourceMock, - 'resourceCollection' => $this->collectionMock, - 'validatorObjectFactory' => $this->validatorObjectFactoryMock, - 'roleFactory' => $this->roleFactoryMock, - 'transportBuilder' => $this->transportBuilderMock, - 'storeManager' => $this->storeManagerMock, - 'validationRules' => $this->validationRulesMock, - 'config' => $this->configMock, - 'encryptor' => $this->encryptorMock, - 'serializer' => $this->serializer ] ); } - /** - * @return void - */ - public function testSendNotificationEmailsIfRequired() - { - $storeId = 0; - $email = 'test1@example.com'; - $origEmail = 'test2@example.com'; - - $password = '1234567'; - $origPassword = '123456789'; - - $username = 'admin1'; - $origUsername = 'admin2'; - - $firstName = 'Foo'; - $lastName = 'Bar'; - - $changes = __('email') . ', ' . __('password') . ', ' . __('username'); - - $this->model->setEmail($email); - $this->model->setOrigData('email', $origEmail); - - $this->model->setPassword($password); - $this->model->setOrigData('password', $origPassword); - - $this->model->setUserName($username); - $this->model->setOrigData('username', $origUsername); - - $this->model->setFirstName($firstName); - $this->model->setLastName($lastName); - - $this->configMock->expects($this->exactly(4)) - ->method('getValue') - ->withConsecutive( - [\Magento\User\Model\User::XML_PATH_USER_NOTIFICATION_TEMPLATE], - [\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_IDENTITY], - [\Magento\User\Model\User::XML_PATH_USER_NOTIFICATION_TEMPLATE], - [\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_IDENTITY] - )->willReturnOnConsecutiveCalls( - 'templateId', - 'sender', - 'templateId', - 'sender' - ); - - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateModel') - ->with($this->equalTo(\Magento\Email\Model\BackendTemplate::class)) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateOptions') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateVars') - ->with(['user' => $this->model, 'store' => $this->storeMock, 'changes' => $changes]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('addTo') - ->withConsecutive( - $this->equalTo($email), - $this->equalTo($firstName . ' ' . $lastName), - $this->equalTo($origEmail), - $this->equalTo($firstName . ' ' . $lastName) - ) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setFrom') - ->with('sender') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateIdentifier') - ->with('templateId') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('getTransport') - ->willReturn($this->transportMock); - $this->transportMock->expects($this->exactly(2))->method('sendMessage'); - - $this->storeManagerMock->expects($this->exactly(2)) - ->method('getStore') - ->with($storeId) - ->willReturn($this->storeMock); - - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->sendNotificationEmailsIfRequired()); - } - - /** - * @return void - */ - public function testSendPasswordResetConfirmationEmail() - { - $storeId = 0; - $email = 'test@example.com'; - $firstName = 'Foo'; - $lastName = 'Bar'; - - $this->model->setEmail($email); - $this->model->setFirstName($firstName); - $this->model->setLastName($lastName); - - $this->configMock->expects($this->at(0)) - ->method('getValue') - ->with(\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_TEMPLATE) - ->willReturn('templateId'); - $this->configMock->expects($this->at(1)) - ->method('getValue') - ->with(\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_IDENTITY) - ->willReturn('sender'); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateModel') - ->with($this->equalTo(\Magento\Email\Model\BackendTemplate::class)) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateOptions') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateVars') - ->with(['user' => $this->model, 'store' => $this->storeMock]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('addTo') - ->with($this->equalTo($email), $this->equalTo($firstName . ' ' . $lastName)) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setFrom') - ->with('sender') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateIdentifier') - ->with('templateId') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('getTransport') - ->willReturn($this->transportMock); - $this->transportMock->expects($this->once())->method('sendMessage'); - - $this->storeManagerMock->expects($this->once()) - ->method('getStore') - ->with($storeId) - ->willReturn($this->storeMock); - - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->sendPasswordResetConfirmationEmail()); - } - - /** - * @return void - */ - public function testVerifyIdentity() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(true); - $this->model->setIsActive(true); - $this->resourceMock->expects($this->once())->method('hasAssigned2Role')->willReturn(true); - $this->assertTrue( - $this->model->verifyIdentity($password), - 'Identity verification failed while should have passed.' - ); - } - - /** - * @return void - */ - public function testVerifyIdentityFailure() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(false); - $this->assertFalse( - $this->model->verifyIdentity($password), - 'Identity verification passed while should have failed.' - ); - } - - /** - * @return void - */ - public function testVerifyIdentityInactiveRecord() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(true); - $this->model->setIsActive(false); - $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); - $this->expectExceptionMessage('The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.'); - $this->model->verifyIdentity($password); - } - - /** - * @return void - */ - public function testVerifyIdentityNoAssignedRoles() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(true); - $this->model->setIsActive(true); - $this->resourceMock->expects($this->once())->method('hasAssigned2Role')->willReturn(false); - $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); - $this->expectExceptionMessage('More permissions are needed to access this.'); - $this->model->verifyIdentity($password); - } - /** * @return void */ @@ -399,225 +69,21 @@ public function testSleep() $this->assertEmpty($expectedResult); } - /** - * @return void - */ - public function testBeforeSave() - { - $this->eventManagerMock->expects($this->any())->method('dispatch'); - $this->model->setIsActive(1); - $actualData = $this->model->beforeSave()->getData(); - $this->assertArrayHasKey('extra', $actualData); - $this->assertArrayHasKey('password', $actualData); - $this->assertArrayHasKey('is_active', $actualData); - } - - /** - * @return void - */ - public function testValidateOk() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - $this->assertTrue($this->model->validate()); - } - - /** - * @return void - */ - public function testValidateInvalid() - { - $messages = ['Invalid username']; - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(false); - $validatorMock->expects($this->once())->method('getMessages')->willReturn($messages); - $this->assertEquals($messages, $this->model->validate()); - } - - /** - * @return void - */ - public function testSaveExtra() - { - $data = [1, 2, 3]; - $this->resourceMock->expects($this->once()) - ->method('saveExtra') - ->with($this->model, json_encode($data)); - - $this->serializer->expects($this->once()) - ->method('serialize') - ->with($data) - ->will($this->returnValue(json_encode($data))); - - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->saveExtra($data)); - } - - /** - * @return void - */ - public function testGetRoles() - { - $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn([]); - $this->assertInternalType('array', $this->model->getRoles()); - } - - /** - * @return void - */ - public function testGetRole() - { - $roles = ['role']; - $roleMock = $this->getMockBuilder(\Magento\Authorization\Model\Role::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($roleMock); - $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn($roles); - $roleMock->expects($this->once())->method('load')->with($roles[0]); - $this->assertInstanceOf(\Magento\Authorization\Model\Role::class, $this->model->getRole()); - } - - /** - * @return void - */ - public function testDeleteFromRole() - { - $this->resourceMock->expects($this->once())->method('deleteFromRole')->with($this->model); - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->deleteFromRole()); - } - - /** - * @return void - */ - public function testRoleUserExistsTrue() - { - $result = ['role']; - $this->resourceMock->expects($this->once())->method('roleUserExists')->with($this->model)->willReturn($result); - $this->assertTrue($this->model->roleUserExists()); - } - - /** - * @return void - */ - public function testRoleUserExistsFalse() - { - $result = []; - $this->resourceMock->expects($this->once())->method('roleUserExists')->with($this->model)->willReturn($result); - $this->assertFalse($this->model->roleUserExists()); - } - - /** - * @return void - */ - public function testGetAclRole() - { - $roles = ['role']; - $result = 1; - $roleMock = $this->getMockBuilder(\Magento\Authorization\Model\Role::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($roleMock); - $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn($roles); - $roleMock->expects($this->once())->method('load')->with($roles[0]); - $roleMock->expects($this->once())->method('getId')->willReturn($result); - $this->assertEquals($result, $this->model->getAclRole()); - } - - /** - * @dataProvider authenticateDataProvider - * @param string $usernameIn - * @param string $usernameOut - * @param bool $expectedResult - * @return void - */ - public function testAuthenticate($usernameIn, $usernameOut, $expectedResult) - { - $password = 'password'; - $config = 'config'; - - $data = ['id' => 1, 'is_active' => 1, 'username' => $usernameOut]; - - $this->configMock->expects($this->once()) - ->method('isSetFlag') - ->with('admin/security/use_case_sensitive_login') - ->willReturn($config); - $this->eventManagerMock->expects($this->any())->method('dispatch'); - - $this->resourceMock->expects($this->any())->method('loadByUsername')->willReturn($data); - $this->model->setIdFieldName('id'); - - $this->encryptorMock->expects($this->any())->method('validateHash')->willReturn(true); - $this->resourceMock->expects($this->any())->method('hasAssigned2Role')->willReturn(true); - $this->assertEquals($expectedResult, $this->model->authenticate($usernameIn, $password)); - } - - /** - * @return array - */ - public function authenticateDataProvider() - { - return [ - 'success' => [ - 'usernameIn' => 'username', - 'usernameOut' => 'username', - 'expectedResult' => true - ], - 'failedUsername' => [ - 'usernameIn' => 'username1', - 'usernameOut' => 'username2', - 'expectedResult' => false - ] - ]; - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @return void - */ - public function testAuthenticateException() - { - $username = 'username'; - $password = 'password'; - $config = 'config'; - - $this->configMock->expects($this->once()) - ->method('isSetFlag') - ->with('admin/security/use_case_sensitive_login') - ->willReturn($config); - - $this->eventManagerMock->expects($this->any())->method('dispatch'); - $this->resourceMock->expects($this->once()) - ->method('loadByUsername') - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__())); - $this->model->authenticate($username, $password); - } - /** * @return void */ public function testChangeResetPasswordLinkToken() { $token = '1'; - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->changeResetPasswordLinkToken($token)); + $this->assertInstanceOf( + User::class, + $this->model->changeResetPasswordLinkToken($token) + ); $this->assertEquals($token, $this->model->getRpToken()); - $this->assertInternalType('string', $this->model->getRpTokenCreatedAt()); + $this->assertInternalType( + 'string', + $this->model->getRpTokenCreatedAt() + ); } /** @@ -640,168 +106,4 @@ public function testIsResetPasswordLinkTokenExpiredIsExpiredToken() $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(0); $this->assertTrue($this->model->isResetPasswordLinkTokenExpired()); } - - /** - * @return void - */ - public function testIsResetPasswordLinkTokenExpiredIsNotExpiredToken() - { - $this->model->setRpToken('1'); - $this->model->setRpTokenCreatedAt( - (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ); - $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(1); - $this->assertFalse($this->model->isResetPasswordLinkTokenExpired()); - } - - public function testCheckPasswordChangeEqualToCurrent() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - - $newPassword = "NEWmYn3wpassw0rd"; - $oldPassword = "OLDmYn3wpassw0rd"; - $this->model->setPassword($newPassword) - ->setId(1) - ->setOrigData('password', $oldPassword); - $this->encryptorMock->expects($this->once()) - ->method('isValidHash') - ->with($newPassword, $oldPassword) - ->willReturn(true); - $result = $this->model->validate(); - $this->assertInternalType('array', $result); - $this->assertCount(1, $result); - $this->assertContains("Sorry, but this password has already been used.", (string)$result[0]); - } - - public function testCheckPasswordChangeEqualToPrevious() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - - $newPassword = "NEWmYn3wpassw0rd"; - $newPasswordHash = "new password hash"; - $oldPassword = "OLDmYn3wpassw0rd"; - $this->model->setPassword($newPassword) - ->setId(1) - ->setOrigData('password', $oldPassword); - $this->encryptorMock->expects($this->atLeastOnce()) - ->method('isValidHash') - ->will($this->onConsecutiveCalls(false, true)); - - $this->resourceMock->expects($this->once())->method('getOldPasswords')->willReturn(['hash1', $newPasswordHash]); - - $result = $this->model->validate(); - $this->assertInternalType('array', $result); - $this->assertCount(1, $result); - $this->assertContains("Sorry, but this password has already been used.", (string)$result[0]); - } - - public function testCheckPasswordChangeValid() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - - $newPassword = "NEWmYn3wpassw0rd"; - $oldPassword = "OLDmYn3wpassw0rd"; - $this->model->setPassword($newPassword) - ->setId(1) - ->setOrigData('password', $oldPassword); - $this->encryptorMock->expects($this->atLeastOnce()) - ->method('isValidHash') - ->will($this->onConsecutiveCalls(false, false, false)); - - $this->resourceMock->expects($this->once())->method('getOldPasswords')->willReturn(['hash1', 'hash2']); - - $result = $this->model->validate(); - $this->assertTrue($result); - } - - /** - * Test for performIdentityCheck method - * - * @param bool $verifyIdentityResult - * @param bool $lockExpires - * @dataProvider dataProviderPerformIdentityCheck - */ - public function testPerformIdentityCheck($verifyIdentityResult, $lockExpires) - { - $password = 'qwerty1'; - $userName = 'John Doe'; - - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn($verifyIdentityResult); - $this->model->setIsActive(true); - $this->resourceMock->expects($this->any())->method('hasAssigned2Role')->willReturn(true); - - $this->model->setUserName($userName); - $this->model->setLockExpires($lockExpires); - - $this->eventManagerMock->expects($this->any()) - ->method('dispatch') - ->with( - 'admin_user_authenticate_after', - [ - 'username' => $userName, - 'password' => $password, - 'user' => $this->model, - 'result' => $verifyIdentityResult - ] - ) - ->willReturnSelf(); - - if ($lockExpires) { - $this->expectException(\Magento\Framework\Exception\State\UserLockedException::class); - $this->expectExceptionMessage((string)__('Your account is temporarily disabled. Please try again later.')); - } - - if (!$lockExpires && !$verifyIdentityResult) { - $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); - $this->expectExceptionMessage( - (string)__('The password entered for the current user is invalid. Verify the password and try again.') - ); - } - - $this->model->performIdentityCheck($password); - } - - /** - * @return array - */ - public function dataProviderPerformIdentityCheck() - { - return [ - ['verifyIdentityResult' => true, 'lockExpires' => false], - ['verifyIdentityResult' => false, 'lockExpires' => false], - ['verifyIdentityResult' => true, 'lockExpires' => true], - ['verifyIdentityResult' => false, 'lockExpires' => true] - ]; - } } diff --git a/app/code/Magento/User/etc/config.xml b/app/code/Magento/User/etc/config.xml index f6a3924b5a2..c1f51bcbece 100644 --- a/app/code/Magento/User/etc/config.xml +++ b/app/code/Magento/User/etc/config.xml @@ -10,6 +10,7 @@ <admin> <emails> <forgot_email_template>admin_emails_forgot_email_template</forgot_email_template> + <new_user_notification_template>admin_emails_new_user_notification_template</new_user_notification_template> <forgot_email_identity>general</forgot_email_identity> <user_notification_template>admin_emails_user_notification_template</user_notification_template> </emails> diff --git a/app/code/Magento/User/etc/di.xml b/app/code/Magento/User/etc/di.xml index b92549b54da..8fc85bc19e5 100644 --- a/app/code/Magento/User/etc/di.xml +++ b/app/code/Magento/User/etc/di.xml @@ -17,4 +17,5 @@ </argument> </arguments> </type> + <preference for="Magento\User\Model\Spi\NotificatorInterface" type="Magento\User\Model\Notificator" /> </config> diff --git a/app/code/Magento/User/etc/email_templates.xml b/app/code/Magento/User/etc/email_templates.xml index b998f304c24..637c2b799f3 100644 --- a/app/code/Magento/User/etc/email_templates.xml +++ b/app/code/Magento/User/etc/email_templates.xml @@ -8,4 +8,10 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Email:etc/email_templates.xsd"> <template id="admin_emails_forgot_email_template" label="Forgot Admin Password" file="password_reset_confirmation.html" type="text" module="Magento_User" area="adminhtml"/> <template id="admin_emails_user_notification_template" label="User Notification" file="user_notification.html" type="text" module="Magento_User" area="adminhtml"/> + <template id="admin_emails_new_user_notification_template" + label="New User Notification" + file="new_user_notification.html" + type="text" + module="Magento_User" + area="adminhtml"/> </config> diff --git a/app/code/Magento/User/view/adminhtml/email/new_user_notification.html b/app/code/Magento/User/view/adminhtml/email/new_user_notification.html new file mode 100644 index 00000000000..891faf5fb8c --- /dev/null +++ b/app/code/Magento/User/view/adminhtml/email/new_user_notification.html @@ -0,0 +1,18 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!--@subject {{trans "New admin user '%user_name' created" user_name=$user.name}} @--> +<!--@vars { +"var store.getFrontendName()|escape":"Store Name" +} @--> + +{{trans "Hello,"}} + +{{trans "A new admin account was created for %first_name, %last_name using %email." first_name=$user.first_name last_name=$user.last_name email=$user.email}} +{{trans "If you have not authorized this action, please contact us immediately at %store_email" store_email=$store_email |escape}}{{depend store_phone}} {{trans "or call us at %store_phone" store_phone=$store_phone |escape}}{{/depend}}. + +{{trans "Thanks,"}} +{{var store.getFrontendName()}} diff --git a/app/code/Magento/Variable/Controller/Adminhtml/System/Variable/Index.php b/app/code/Magento/Variable/Controller/Adminhtml/System/Variable/Index.php index f70cb31eb54..71cb76a67d1 100644 --- a/app/code/Magento/Variable/Controller/Adminhtml/System/Variable/Index.php +++ b/app/code/Magento/Variable/Controller/Adminhtml/System/Variable/Index.php @@ -6,12 +6,14 @@ */ namespace Magento\Variable\Controller\Adminhtml\System\Variable; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + /** * Display Variables list page * @api * @since 100.0.2 */ -class Index extends \Magento\Variable\Controller\Adminhtml\System\Variable +class Index extends \Magento\Variable\Controller\Adminhtml\System\Variable implements HttpGetActionInterface { /** * Index Action diff --git a/app/code/Magento/Vault/etc/db_schema.xml b/app/code/Magento/Vault/etc/db_schema.xml index ed1f2adba21..e9645e211ae 100644 --- a/app/code/Magento/Vault/etc/db_schema.xml +++ b/app/code/Magento/Vault/etc/db_schema.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="vault_payment_token" resource="default" engine="innodb" comment="Vault tokens of payment"> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" - comment="Entity Id"/> + comment="Entity ID"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> <column xsi:type="varchar" name="public_hash" nullable="false" length="128" diff --git a/app/code/Magento/Version/Controller/Index/Index.php b/app/code/Magento/Version/Controller/Index/Index.php index c5d02cd9a1f..0db9b5f80d4 100644 --- a/app/code/Magento/Version/Controller/Index/Index.php +++ b/app/code/Magento/Version/Controller/Index/Index.php @@ -6,6 +6,7 @@ */ namespace Magento\Version\Controller\Index; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; use Magento\Framework\App\ProductMetadataInterface; @@ -13,7 +14,7 @@ /** * Magento Version controller */ -class Index extends Action +class Index extends Action implements HttpGetActionInterface { /** * @var ProductMetadataInterface diff --git a/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php b/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php index 1d8ccc127ce..d1a3c0c0f08 100644 --- a/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php +++ b/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php @@ -197,7 +197,7 @@ private function isPropertyDeclaredInDataObject( $index = array_search($serviceMethodParamName, array_column($methodParams, 'name')); if ($index !== false) { $paramObjectType = $methodParams[$index][MethodsMap::METHOD_META_TYPE]; - $setter = 'set' . ucfirst(SimpleDataObjectConverter::snakeCaseToCamelCase($objectProperty)); + $setter = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($objectProperty); if (array_key_exists( $setter, $this->getMethodsMap()->getMethodsMap($paramObjectType) diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index e6df38c563e..e74db459808 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -95,6 +95,13 @@ public function resolve() $this->requestValidator->validate(); $webapiResolvedParams = []; $inputData = $this->request->getRequestData(); + + $httpMethod = $this->request->getHttpMethod(); + if ($httpMethod == \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE) { + $requestBodyParams = $this->request->getBodyParams(); + $inputData = array_merge($requestBodyParams, $inputData); + } + foreach ($inputData as $key => $singleEntityParams) { $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams); } @@ -103,6 +110,8 @@ public function resolve() } /** + * Returns route. + * * @return \Magento\Webapi\Controller\Rest\Router\Route */ public function getRoute() @@ -111,8 +120,7 @@ public function getRoute() } /** - * Convert the input array from key-value format to a list of parameters - * suitable for the specified class / method. + * Convert the input array from key-value format to a list of parameters suitable for the specified class / method. * * Instead of \Magento\Webapi\Controller\Rest\InputParamsResolver * we don't need to merge body params with url params and use only body params diff --git a/app/code/Magento/Weee/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/Helper/ProcessTaxAttribute.php b/app/code/Magento/Weee/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/Helper/ProcessTaxAttribute.php new file mode 100644 index 00000000000..4aa941cd137 --- /dev/null +++ b/app/code/Magento/Weee/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/Helper/ProcessTaxAttribute.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Weee\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; +use Magento\Framework\App\RequestInterface; + +/** + * Handles product tax attributes data initialization. + */ +class ProcessTaxAttribute +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @param RequestInterface $request + */ + public function __construct(RequestInterface $request) + { + $this->request = $request; + } + + /** + * Handles product tax attributes data initialization. + * + * @param Helper $subject + * @param Product $result + * @param Product $product + * @param array $productData + * @return Product + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterInitializeFromData( + Helper $subject, + Product $result, + Product $product, + array $productData + ): Product { + $attributes = $result->getAttributes(); + if (!empty($attributes)) { + foreach ($attributes as $attribute) { + if ($attribute->getFrontendInput() == 'weee' && !isset($productData[$attribute->getAttributeCode()])) { + $result->setData($attribute->getAttributeCode(), []); + } + } + } + + return $result; + } +} diff --git a/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml b/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml index eee3f421910..ebf9f410471 100644 --- a/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml +++ b/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductAddFPTValueSection"> <element name="addFPT" type="button" selector="[data-index='{{FPTAttributeCode}}'] [data-action='add_new_row']" parameterized="true"/> + <element name="removeRowByIndex" type="button" selector="[data-index='{{FPTAttributeCode}}'] [data-action='remove_row']:nth-of-type({{rowIndex}})" parameterized="true"/> <element name="selectCountryForFPT" type="select" selector="(//select[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[country]')])[last()]" parameterized="true"/> <element name="selectStateForFPT" type="select" selector="(//select[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[state]')])[last()]" parameterized="true"/> <element name="setTaxValueForFPT" type="text" selector="(//input[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[value]')])[last()]" parameterized="true"/> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml new file mode 100644 index 00000000000..2e3467fe2c7 --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveProductWeeeAttributeOptionTest"> + <annotations> + <features value="Weee attribute options can be removed in product page"/> + <title value="Weee attribute options can be removed in product page"/> + <description value="Weee attribute options can be removed in product page"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-95033"/> + <group value="weee"/> + </annotations> + <before> + <createData entity="productFPTAttribute" stepKey="createProductFPTAttribute"/> + <createData entity="AddToDefaultSet" stepKey="addFPTToAttributeSet"> + <requiredEntity createDataKey="createProductFPTAttribute"/> + </createData> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProductInitial"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProductInitial"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AdminProductAddFPTValueActionGroup" stepKey="addWeeeAttributeValue"> + <argument name="FPTAttributeCode" value="$$createProductFPTAttribute.attribute_code$$"/> + <argument name="stateForFPT" value="California"/> + <argument name="valueForFPT" value="10"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProductInitial"/> + </before> + <after> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductListing"/> + <waitForPageLoad stepKey="waitForProductListingPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + </after> + <!-- Test Steps --> + <!-- Step 1: Open created product edit page --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <!-- Step 2: Remove weee attribute options --> + <click selector="{{AdminProductAddFPTValueSection.removeRowByIndex('$$createProductFPTAttribute.attribute_code$$','1')}}" stepKey="removeAttributeOption"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <!-- Assert weee attribute options are empty --> + <dontSeeElement selector="{{AdminProductAddFPTValueSection.removeRowByIndex('$$createProductFPTAttribute.attribute_code$$','1')}}" stepKey="dontSeeOptions"/> + </test> +</tests> diff --git a/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php b/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php index c96a7a02003..d05b8c21689 100644 --- a/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php +++ b/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php @@ -85,7 +85,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { @@ -93,7 +93,7 @@ public function modifyData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -155,6 +155,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'dndConfig' => [ 'enabled' => false, ], + 'required' => (bool)$attributeConfig['arguments']['data']['config']['required'], ], ], ], @@ -180,6 +181,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'component' => 'Magento_Weee/js/fpt-group', 'visible' => true, 'label' => __('Country/State'), + 'showLabel' => false, ], ], ], @@ -197,6 +199,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'validation' => [ 'required-entry' => true, ], + 'showLabel' => false, ], ], ], @@ -216,6 +219,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) ], 'caption' => '*', 'visible' => true, + 'showLabel' => false, ], ], ], @@ -233,6 +237,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'validation' => [ 'validate-fpt-group' => true ], + 'showLabel' => false, ], ], ], @@ -252,6 +257,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'validation' => [ 'required-entry' => true ], + 'showLabel' => false, ], ], ], @@ -267,6 +273,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'label' => __('Website'), 'visible' => $this->websiteManager->isMultiWebsites(), 'options' => $this->websiteManager->getWebsites($product, $eavAttribute), + 'showLabel' => false, ], ], ], diff --git a/app/code/Magento/Weee/etc/db_schema.xml b/app/code/Magento/Weee/etc/db_schema.xml index 96c3be9740e..49b201acb33 100644 --- a/app/code/Magento/Weee/etc/db_schema.xml +++ b/app/code/Magento/Weee/etc/db_schema.xml @@ -13,7 +13,7 @@ <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" default="0" comment="Website Id"/> <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="false" - default="0" comment="Entity Id"/> + default="0" comment="Entity ID"/> <column xsi:type="varchar" name="country" nullable="true" length="2" comment="Country"/> <column xsi:type="decimal" name="value" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Value"/> diff --git a/app/code/Magento/Weee/etc/di.xml b/app/code/Magento/Weee/etc/di.xml index e52aebd3af8..8b433163cad 100644 --- a/app/code/Magento/Weee/etc/di.xml +++ b/app/code/Magento/Weee/etc/di.xml @@ -78,4 +78,7 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper"> + <plugin name="weeeAttributeOptionsProcess" type="Magento\Weee\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper\ProcessTaxAttribute"/> + </type> </config> diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php index d813e944373..45b3056eac6 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php @@ -11,6 +11,9 @@ */ namespace Magento\Widget\Block\Adminhtml\Widget; +/** + * Chooser widget block. + */ class Chooser extends \Magento\Backend\Block\Template { /** @@ -180,7 +183,7 @@ protected function _toHtml() <label class="widget-option-label" id="' . $chooserId . 'label">' . - ($this->getLabel() ? $this->getLabel() : __( + ($this->getLabel() ? $this->escapeHtml($this->getLabel()) : __( 'Not Selected' )) . '</label> diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/BuildWidget.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/BuildWidget.php index d9ef20aa90e..0b9431f717e 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/BuildWidget.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/BuildWidget.php @@ -6,7 +6,9 @@ */ namespace Magento\Widget\Controller\Adminhtml\Widget; -class BuildWidget extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class BuildWidget extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php index 61e44fe00db..e7454faf925 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Widget\Controller\Adminhtml\Widget; -class Index extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Index extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Index.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Index.php index 40ade57f6a9..50f3addaf7c 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Index.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Index.php @@ -6,7 +6,9 @@ */ namespace Magento\Widget\Controller\Adminhtml\Widget\Instance; -class Index extends \Magento\Widget\Controller\Adminhtml\Widget\Instance +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; + +class Index extends \Magento\Widget\Controller\Adminhtml\Widget\Instance implements HttpGetActionInterface { /** * Widget Instances Grid diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php index b5d31dc10be..dc137365498 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php @@ -6,7 +6,9 @@ */ namespace Magento\Widget\Controller\Adminhtml\Widget\Instance; -class Save extends \Magento\Widget\Controller\Adminhtml\Widget\Instance +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; + +class Save extends \Magento\Widget\Controller\Adminhtml\Widget\Instance implements HttpPostActionInterface { /** * Save action diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/LoadOptions.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/LoadOptions.php index 054019079eb..03d9d103113 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/LoadOptions.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/LoadOptions.php @@ -6,9 +6,11 @@ */ namespace Magento\Widget\Controller\Adminhtml\Widget; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\ObjectManager; -class LoadOptions extends \Magento\Backend\App\Action +class LoadOptions extends \Magento\Backend\App\Action implements HttpGetActionInterface, HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Wishlist/Block/AbstractBlock.php b/app/code/Magento/Wishlist/Block/AbstractBlock.php index 8b4a8df1bf9..bb8138fb87a 100644 --- a/app/code/Magento/Wishlist/Block/AbstractBlock.php +++ b/app/code/Magento/Wishlist/Block/AbstractBlock.php @@ -231,9 +231,21 @@ public function hasDescription($item) * Retrieve formated Date * * @param string $date + * @deprecated * @return string */ public function getFormatedDate($date) + { + return $this->getFormattedDate($date); + } + + /** + * Retrieve formatted Date + * + * @param string $date + * @return string + */ + public function getFormattedDate($date) { return $this->formatDate($date, \IntlDateFormatter::MEDIUM); } diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml index 145fb94f356..c9a0c0cc0f8 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml @@ -13,10 +13,11 @@ <stories value="Adding to wishlist"/> <title value="Customer should be able to add products to wishlist from different stores"/> <description value="All products added to wishlist should be visible on any store. Even if product visibility was set to 'Not Visible Individually' for this store"/> - <!-- Skipped because of MAGETWO-93980 --> - <group value="skip"/> <group value="wishlist"/> <severity value="AVERAGE"/> + <skip> + <issueId value="MAGETWO-93980"/> + </skip> </annotations> <before> <createData entity="customStoreGroup" stepKey="storeGroup"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml index 0ae2b6af804..0001bd9d6db 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml @@ -13,12 +13,12 @@ <stories value="Delete a persist wishlist for a customer"/> <title value="Customer should be able to delete a persistent wishlist"/> <description value="Customer should be able to delete a persistent wishlist"/> + <severity value="AVERAGE"/> <group value="wishlist"/> - <group value="skip"/> + <testCaseId value="MC-4110"/> <skip> <issueId value="MQE-1145"/> </skip> - <severity value="AVERAGE"/> </annotations> <before> <createData stepKey="category" entity="SimpleSubCategory"/> diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/images/logo-magento.png b/app/design/adminhtml/Magento/backend/Magento_Backend/web/images/logo-magento.png index d9395c938cb..0cca183e08d 100644 Binary files a/app/design/adminhtml/Magento/backend/Magento_Backend/web/images/logo-magento.png and b/app/design/adminhtml/Magento/backend/Magento_Backend/web/images/logo-magento.png differ 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 c87b8f2ca5c..b3e4a91180b 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 @@ -59,7 +59,11 @@ } .abs-field-no-label { + /** + *@codingStandardsIgnoreStart + */ #mix-grid .return_length(@field-label-grid__column, @field-grid__columns, '+'); + //@codingStandardsIgnoreEnd margin-left: @_length; } @@ -166,6 +170,13 @@ .admin__control-text, .admin__control-textarea { width: 100%; + &.disabled { + background-color: #e9e9e9; + border-color: #adadad; + color: #303030; + cursor: not-allowed; + opacity: .5; + } } } diff --git a/app/design/frontend/Magento/blank/web/images/logo.svg b/app/design/frontend/Magento/blank/web/images/logo.svg index 013d6e7c5a1..0f29d4e3eef 100644 --- a/app/design/frontend/Magento/blank/web/images/logo.svg +++ b/app/design/frontend/Magento/blank/web/images/logo.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="55.139px" viewBox="0 0 189 55.139" width="189px" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 189 55.139"><path d="m23.333 0l-23.333 14.135v26.865l6.06 3.568v-26.865l17.278-10.504 17.292 10.488 0.074 0.042-0.008 26.798 6.001-3.527v-26.865l-23.364-14.135zm3.088 16.538v31.407l-3.088 1.889-3.091-1.896v-31.376l-8.003 4.928v26.86l11.094 6.789 11.189-6.837v-26.829l-8.101-4.935z" fill="#ED7402"/><path d="m12.239 21.491l8.003 4.886v-9.814l-8.003 4.928zm14.182-4.953v9.902l8.101-4.967-8.101-4.935zm20.276-2.403l-23.364-14.135-23.333 14.135 6.06 3.568 17.278-10.504 17.365 10.53 5.994-3.594z" fill="#F8B97F"/><path d="m82.328 39.984l-1.608-20.54-8.156 20.651h-2.656l-8.156-20.651-1.571 20.541h-3.293l2.058-25.814h4.34l8.043 21.174 8.044-21.174h4.301l2.021 25.814h-3.367z" fill="#131108"/><path d="m99.91 39.984l-0.375-2.396c-1.421 1.458-3.366 2.769-6.284 2.769-3.218 0-5.237-1.945-5.237-4.977 0-4.452 3.815-6.209 11.261-6.996v-0.748c0-2.244-1.348-3.03-3.406-3.03-2.17 0-4.227 0.673-6.172 1.533l-0.449-2.88c2.133-0.861 4.153-1.496 6.922-1.496 4.339 0 6.436 1.757 6.436 5.723v12.498h-2.7zm-0.635-9.055c-6.586 0.636-7.97 2.432-7.97 4.266 0 1.457 0.973 2.394 2.657 2.394 1.945 0 3.815-0.974 5.312-2.508v-4.152h0.001z" fill="#131108"/><path d="m121.72 21.876l0.485 2.992-3.404 0.336c0.486 0.824 0.712 1.76 0.712 2.769 0 3.817-3.219 6.134-6.848 6.134-0.449 0-0.898-0.037-1.348-0.111-0.523 0.338-0.897 0.75-0.897 1.085 0 0.636 0.634 0.787 3.777 1.349l1.272 0.223c3.778 0.673 6.135 1.871 6.135 4.639 0 3.741-4.078 5.5-8.715 5.5-4.64 0-8.343-1.458-8.343-4.6 0-1.834 1.271-3.256 3.777-4.603-0.784-0.562-1.121-1.198-1.121-1.872 0-0.861 0.672-1.722 1.87-2.432-1.982-0.973-3.33-2.879-3.33-5.312 0-3.852 3.217-6.208 6.846-6.208 1.795 0 3.368 0.522 4.604 1.496l4.53-1.385zm-13.99 20.052c0 1.424 1.834 2.471 5.312 2.471 3.48 0 5.424-1.197 5.424-2.693 0-1.086-0.822-1.832-3.365-2.282l-2.134-0.375c-0.972-0.187-1.495-0.299-2.207-0.448-2.1 1.045-3.03 2.094-3.03 3.327zm4.86-17.733c-2.244 0-3.629 1.722-3.629 3.891 0 2.058 1.421 3.665 3.629 3.665 2.283 0 3.704-1.682 3.704-3.815 0.01-2.132-1.49-3.741-3.7-3.741z" fill="#131108"/><path d="m137.58 31.416h-12.122c0.112 4.15 2.094 6.098 5.2 6.098 2.583 0 4.453-1.009 6.397-2.544l0.484 2.994c-1.907 1.497-4.188 2.394-7.143 2.394-4.642 0-8.271-2.807-8.271-9.354 0-5.724 3.368-9.239 7.856-9.239 5.199 0 7.595 4.002 7.595 8.94v0.711zm-7.63-7.033c-2.059 0-3.817 1.459-4.34 4.526h8.604c-0.41-2.881-1.68-4.526-4.26-4.526z" fill="#131108"/><path d="m151.61 39.984v-12.16c0-1.832-0.786-3.067-2.73-3.067-1.759 0-3.555 1.161-5.163 2.88v12.347h-3.329v-17.846h2.655l0.412 2.582c1.683-1.533 3.78-2.955 6.321-2.955 3.369 0 5.166 2.019 5.166 5.236v12.983h-3.33z" fill="#131108"/><path d="m164.78 40.284c-3.143 0-5.199-1.124-5.199-4.716v-10.624h-2.694v-2.806h2.694v-5.949l3.255-0.485v6.434h3.853l0.449 2.806h-4.302v10.025c0 1.461 0.599 2.357 2.47 2.357 0.598 0 1.121-0.035 1.531-0.112l0.45 2.843c-0.57 0.112-1.35 0.227-2.51 0.227z" fill="#131108"/><path d="m175.82 40.357c-4.752 0-8.194-3.403-8.194-9.278 0-5.874 3.442-9.314 8.194-9.314 4.787 0 8.305 3.44 8.305 9.314 0 5.875-3.52 9.278-8.3 9.278zm0-15.788c-3.217 0-4.826 2.769-4.826 6.51 0 3.668 1.683 6.511 4.826 6.511 3.291 0 4.938-2.77 4.938-6.511-0.01-3.666-1.73-6.51-4.94-6.51z" fill="#131108"/><path d="m186.48 24.81c-1.338 0-2.268-0.929-2.268-2.318 0-1.379 0.95-2.328 2.268-2.328 1.34 0 2.27 0.939 2.27 2.328 0 1.378-0.95 2.318-2.27 2.318zm0-4.376c-1.078 0-1.938 0.739-1.938 2.058 0 1.31 0.859 2.049 1.938 2.049 1.09 0 1.949-0.739 1.949-2.049 0-1.319-0.87-2.058-1.95-2.058zm0.67 3.297l-0.768-1.099h-0.249v1.059h-0.44v-2.568h0.779c0.54 0 0.898 0.27 0.898 0.75 0 0.37-0.201 0.609-0.52 0.709l0.74 1.049-0.44 0.1zm-0.68-2.209h-0.34v0.759h0.319c0.29 0 0.471-0.12 0.471-0.379 0-0.25-0.16-0.38-0.45-0.38z" fill="#131108"/></svg> \ No newline at end of file +<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.073 50"><style>.st0{fill:#f26322}.st1{fill:#4d4d4d}</style><path class="st0" d="M21.432 0L0 12.373v24.713l6.117 3.533-.041-24.713 15.315-8.842 15.313 8.842v24.706l6.118-3.526V12.35z"/><path class="st0" d="M24.47 40.618l-3.058 1.772-3.071-1.759V15.906l-6.116 3.532.01 24.712 9.172 5.298 9.18-5.298V19.438l-6.117-3.532z"/><path class="st1" d="M56.838 12.522l8.415 21.258h.068l8.21-21.258h3.203V36.88h-2.215V15.656h-.068c-.114.386-.239.772-.374 1.158-.115.318-.246.67-.393 1.055-.147.387-.278.75-.391 1.09l-7.052 17.919h-2.01L57.11 18.96a19.913 19.913 0 0 1-.408-1.039 43.558 43.558 0 0 1-.375-1.073 68.067 68.067 0 0 0-.408-1.192h-.069v21.223h-2.112V12.522h3.1zM83.17 36.982a5.205 5.205 0 0 1-1.823-.92 4.327 4.327 0 0 1-1.209-1.533 4.881 4.881 0 0 1-.443-2.145 5.018 5.018 0 0 1 .579-2.556 4.472 4.472 0 0 1 1.568-1.584 7.927 7.927 0 0 1 2.299-.903 24.732 24.732 0 0 1 2.811-.477 36 36 0 0 0 2.198-.29 6.689 6.689 0 0 0 1.465-.392c.329-.123.614-.343.817-.63.187-.325.276-.698.256-1.073v-.34a3.212 3.212 0 0 0-1.09-2.674 4.93 4.93 0 0 0-3.134-.87c-3.135 0-4.781 1.306-4.941 3.918h-2.077a5.748 5.748 0 0 1 1.891-4.089 7.5 7.5 0 0 1 5.127-1.533 7.335 7.335 0 0 1 4.564 1.278 4.923 4.923 0 0 1 1.67 4.173v9.573c-.034.402.069.804.29 1.141.219.251.536.394.868.392.12-.001.24-.012.358-.034.124-.022.266-.057.425-.102h.103v1.533c-.188.077-.382.14-.58.188-.28.062-.566.091-.852.086a2.694 2.694 0 0 1-1.839-.597 2.575 2.575 0 0 1-.75-1.891v-.374h-.102c-.277.372-.578.725-.903 1.056-.38.385-.81.717-1.278.988a7.14 7.14 0 0 1-1.737.715 8.443 8.443 0 0 1-2.249.272 8.186 8.186 0 0 1-2.282-.306m5.195-1.857a5.971 5.971 0 0 0 1.857-1.175 4.767 4.767 0 0 0 1.499-3.441V27.34a7.295 7.295 0 0 1-2.061.732 33.21 33.21 0 0 1-2.504.426c-.749.114-1.441.233-2.077.358a5.237 5.237 0 0 0-1.652.596 3.055 3.055 0 0 0-1.108 1.107 3.579 3.579 0 0 0-.408 1.824c-.018.53.093 1.056.324 1.533.201.392.493.73.851.987a3.35 3.35 0 0 0 1.244.529c.493.104.995.155 1.499.153a6.555 6.555 0 0 0 2.536-.46M99.35 41.734a4.687 4.687 0 0 1-2.009-3.287h2.043c.14.966.763 1.794 1.652 2.197a7.522 7.522 0 0 0 3.288.664 5.688 5.688 0 0 0 4.173-1.345 4.997 4.997 0 0 0 1.345-3.697v-2.793h-.102a7.293 7.293 0 0 1-2.283 2.282 6.297 6.297 0 0 1-3.304.784 7.375 7.375 0 0 1-3.135-.647 6.921 6.921 0 0 1-2.385-1.806 8.095 8.095 0 0 1-1.516-2.777 11.429 11.429 0 0 1-.528-3.56 10.89 10.89 0 0 1 .613-3.799 8.483 8.483 0 0 1 1.636-2.777 6.75 6.75 0 0 1 2.402-1.703 7.45 7.45 0 0 1 2.913-.58 6.234 6.234 0 0 1 3.372.835 6.938 6.938 0 0 1 2.215 2.265h.102v-2.725h2.078v16.931a6.78 6.78 0 0 1-1.636 4.735 7.751 7.751 0 0 1-5.893 2.112 8.337 8.337 0 0 1-5.041-1.309m9.232-8.908a8.565 8.565 0 0 0 1.397-5.11 11.22 11.22 0 0 0-.34-2.862 6.239 6.239 0 0 0-1.056-2.231 4.828 4.828 0 0 0-1.789-1.448 5.772 5.772 0 0 0-2.503-.511 4.782 4.782 0 0 0-4.071 1.941 8.464 8.464 0 0 0-1.448 5.179c-.008.936.107 1.87.34 2.777a6.64 6.64 0 0 0 1.022 2.214 4.81 4.81 0 0 0 1.703 1.465 5.208 5.208 0 0 0 2.42.528 4.967 4.967 0 0 0 4.325-1.942M119.244 36.624a7.19 7.19 0 0 1-2.572-1.941 8.66 8.66 0 0 1-1.583-2.93 11.839 11.839 0 0 1-.546-3.662 11.179 11.179 0 0 1 .58-3.663 9.138 9.138 0 0 1 1.617-2.929 7.307 7.307 0 0 1 2.522-1.942 7.684 7.684 0 0 1 3.321-.698 7.275 7.275 0 0 1 3.56.8 6.678 6.678 0 0 1 2.351 2.146 8.806 8.806 0 0 1 1.278 3.083 16.87 16.87 0 0 1 .374 3.577h-13.422c.013.941.157 1.875.426 2.777a6.968 6.968 0 0 0 1.124 2.231 5.108 5.108 0 0 0 1.857 1.499 5.948 5.948 0 0 0 2.623.546 4.985 4.985 0 0 0 3.424-1.074 5.875 5.875 0 0 0 1.719-2.878h2.044a7.51 7.51 0 0 1-2.385 4.19 7.072 7.072 0 0 1-4.803 1.567 8.386 8.386 0 0 1-3.509-.699m8.312-12.264a5.986 5.986 0 0 0-.988-1.976 4.525 4.525 0 0 0-1.635-1.311 5.362 5.362 0 0 0-2.351-.478 5.623 5.623 0 0 0-2.368.478 5.064 5.064 0 0 0-1.754 1.311 6.566 6.566 0 0 0-1.141 1.96 9.615 9.615 0 0 0-.562 2.453h11.174a9.268 9.268 0 0 0-.375-2.437M134.879 19.267v2.691h.068a7.237 7.237 0 0 1 2.333-2.197 6.798 6.798 0 0 1 3.561-.868 5.834 5.834 0 0 1 4.037 1.413 5.174 5.174 0 0 1 1.584 4.071V36.88h-2.112V24.581a3.716 3.716 0 0 0-1.073-2.947 4.334 4.334 0 0 0-2.948-.937 5.896 5.896 0 0 0-2.111.375 5.558 5.558 0 0 0-1.738 1.039 4.717 4.717 0 0 0-1.601 3.593V36.88h-2.112V19.267h2.112zM151.912 36.284a2.934 2.934 0 0 1-.92-2.436V21.005h-2.657v-1.738h2.657v-5.416h2.112v5.416h3.271v1.738h-3.271v12.502a1.65 1.65 0 0 0 .426 1.312c.371.265.823.391 1.277.357.258-.001.515-.029.766-.085.215-.043.426-.106.63-.188h.103v1.806a5.907 5.907 0 0 1-1.942.306 3.819 3.819 0 0 1-2.452-.731M162.625 36.624a7.368 7.368 0 0 1-2.571-1.942 8.732 8.732 0 0 1-1.618-2.929 12.217 12.217 0 0 1 0-7.324 8.744 8.744 0 0 1 1.618-2.93 7.386 7.386 0 0 1 2.571-1.942 8.106 8.106 0 0 1 3.424-.698 7.989 7.989 0 0 1 3.406.698 7.424 7.424 0 0 1 2.556 1.942 8.504 8.504 0 0 1 1.601 2.93 12.57 12.57 0 0 1 0 7.324 8.5 8.5 0 0 1-1.601 2.929 7.415 7.415 0 0 1-2.556 1.942 7.959 7.959 0 0 1-3.406.698 8.075 8.075 0 0 1-3.424-.698m6.013-1.652a5.308 5.308 0 0 0 1.873-1.601 7.215 7.215 0 0 0 1.124-2.385 11.348 11.348 0 0 0 0-5.792 7.215 7.215 0 0 0-1.124-2.385 5.289 5.289 0 0 0-1.873-1.601 6.109 6.109 0 0 0-5.195 0 5.497 5.497 0 0 0-1.874 1.601 7.046 7.046 0 0 0-1.141 2.385 11.392 11.392 0 0 0 0 5.792c.227.86.614 1.669 1.141 2.385a5.817 5.817 0 0 0 7.069 1.601M176.856 22.191a2.128 2.128 0 0 1-2.213-2.265 2.216 2.216 0 1 1 4.431 0 2.146 2.146 0 0 1-2.218 2.265m0-4.277a1.845 1.845 0 0 0-1.892 2.012 1.9 1.9 0 1 0 3.797 0 1.854 1.854 0 0 0-1.905-2.012m.653 3.222l-.751-1.073h-.243v1.035h-.43v-2.509h.763c.526 0 .877.264.877.732a.681.681 0 0 1-.508.693l.724 1.025-.432.097zm-.661-2.157h-.333v.741h.312c.283 0 .46-.117.46-.371.001-.244-.158-.37-.439-.37"/></svg> diff --git a/app/etc/di.xml b/app/etc/di.xml index 8e050dec6eb..db979f9b763 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -206,8 +206,6 @@ <preference for="Magento\Framework\MessageQueue\ExchangeFactoryInterface" type="Magento\Framework\MessageQueue\ExchangeFactory" /> <preference for="Magento\Framework\MessageQueue\Bulk\ExchangeFactoryInterface" type="Magento\Framework\MessageQueue\Bulk\ExchangeFactory" /> <preference for="Magento\Framework\MessageQueue\QueueFactoryInterface" type="Magento\Framework\MessageQueue\QueueFactory" /> - <preference for="Magento\Framework\App\Request\ValidatorInterface" - type="Magento\Framework\App\Request\CsrfValidator" /> <preference for="Magento\Framework\Search\Request\IndexScopeResolverInterface" type="Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver"/> <type name="Magento\Framework\Model\ResourceModel\Db\TransactionManager" shared="false" /> <type name="Magento\Framework\Acl\Data\Cache"> @@ -1695,4 +1693,32 @@ <argument name="scopeType" xsi:type="const">Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT</argument> </arguments> </type> + <virtualType name="CsrfRequestValidator" type="Magento\Framework\App\Request\CsrfValidator" /> + <virtualType name="RequestValidator" type="Magento\Framework\App\Request\CompositeValidator"> + <arguments> + <argument name="validators" xsi:type="array"> + <item name="csrf_validator" xsi:type="object">CsrfRequestValidator</item> + <item name="http_method_validator" xsi:type="object"> + Magento\Framework\App\Request\HttpMethodValidator + </item> + </argument> + </arguments> + </virtualType> + <preference for="Magento\Framework\App\Request\ValidatorInterface" type="RequestValidator" /> + <type name="Magento\Framework\App\Request\HttpMethodMap"> + <arguments> + <argument name="map" xsi:type="array"> + <item name="OPTIONS" xsi:type="string">\Magento\Framework\App\Action\HttpOptionsActionInterface</item> + <item name="GET" xsi:type="string">\Magento\Framework\App\Action\HttpGetActionInterface</item> + <item name="HEAD" xsi:type="string">\Magento\Framework\App\Action\HttpHeadActionInterface</item> + <item name="POST" xsi:type="string">\Magento\Framework\App\Action\HttpPostActionInterface</item> + <item name="PUT" xsi:type="string">\Magento\Framework\App\Action\HttpPutActionInterface</item> + <item name="PATCH" xsi:type="string">\Magento\Framework\App\Action\HttpPatchActionInterface</item> + <item name="DELETE" xsi:type="string">\Magento\Framework\App\Action\HttpDeleteActionInterface</item> + <item name="CONNECT" xsi:type="string">\Magento\Framework\App\Action\HttpConnectActionInterface</item> + <item name="PROPFIND" xsi:type="string">\Magento\Framework\App\Action\HttpPropfindActionInterface</item> + <item name="TRACE" xsi:type="string">\Magento\Framework\App\Action\HttpTraceActionInterface</item> + </argument> + </arguments> + </type> </config> diff --git a/composer.json b/composer.json index e2a646275d9..3f8f0a033c8 100644 --- a/composer.json +++ b/composer.json @@ -221,6 +221,7 @@ "magento/module-tax": "*", "magento/module-tax-import-export": "*", "magento/module-theme": "*", + "magento/module-theme-graph-ql": "*", "magento/module-translation": "*", "magento/module-ui": "*", "magento/module-ups": "*", diff --git a/composer.lock b/composer.lock index 2550f70f0be..1d101c8aaaf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "18982aa4d36bcfd22cf073dfb578efdb", + "content-hash": "d6640ddfd342feceaec44c406c056f57", "packages": [ { "name": "braintree/braintree_php", diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php index f9442d8b649..1d37ea9a2fc 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php @@ -111,6 +111,9 @@ public function addDataProvider() 'option_with_value_node_that_starts_with_a_number' => [ array_merge($optionPayload, [AttributeOptionInterface::VALUE => '123_some_text']) ], + 'option_with_value_node_that_is_a_number' => [ + array_merge($optionPayload, [AttributeOptionInterface::VALUE => '123']) + ], ]; } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 8841fab7579..eff2e96f4b1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -225,7 +225,9 @@ public function testCategoryProducts() } short_description sku - small_image + small_image { + path + } small_image_label special_from_date special_price diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryWithDescriptionDirectivesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryWithDescriptionDirectivesTest.php new file mode 100644 index 00000000000..c115f7124c9 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryWithDescriptionDirectivesTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Catalog; + +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for checking that category description directives are rendered correctly + */ +class CategoryWithDescriptionDirectivesTest extends GraphQlAbstract +{ + /** + * @var \Magento\TestFramework\ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/category.php + */ + public function testHtmlDirectivesRendered() + { + $categoryId = 333; + $mediaFilePath = '/path/to/mediafile'; + /** @var StoreManagerInterface $storeManager */ + $storeManager = ObjectManager::getInstance()->get(StoreManagerInterface::class); + $storeBaseUrl = $storeManager->getStore()->getBaseUrl(); + + /* Remove index.php from base URL */ + $storeBaseUrlParts = explode('/index.php', $storeBaseUrl); + $storeBaseUrl = $storeBaseUrlParts[0]; + + /** @var CategoryRepositoryInterface $categoryRepository */ + $categoryRepository = ObjectManager::getInstance()->get(CategoryRepositoryInterface::class); + /** @var CategoryInterface $category */ + $category = $categoryRepository->get($categoryId); + $category->setDescription('Test: {{media url="' . $mediaFilePath . '"}}'); + $categoryRepository->save($category); + + $query = <<<QUERY +{ + category(id: {$categoryId}) { + description + } +} +QUERY; + $response = $this->graphQlQuery($query); + + self::assertNotContains('media url', $response['category']['description']); + self::assertContains($storeBaseUrl, $response['category']['description']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php new file mode 100644 index 00000000000..8da2702917a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Catalog; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class MediaGalleryTest extends GraphQlAbstract +{ + /** + * @var \Magento\TestFramework\ObjectManager + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php + */ + public function testProductSmallImageUrlWithExistingImage() + { + $productSku = 'simple'; + $query = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) { + items { + small_image { + url + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('url', $response['products']['items'][0]['small_image']); + self::assertContains('magento_image.jpg', $response['products']['items'][0]['small_image']['url']); + self::assertTrue($this->checkImageExists($response['products']['items'][0]['small_image']['url'])); + } + + /** + * @param string $url + * @return bool + */ + private function checkImageExists(string $url): bool + { + $connection = curl_init($url); + curl_setopt($connection, CURLOPT_HEADER, true); + curl_setopt($connection, CURLOPT_NOBODY, true); + curl_setopt($connection, CURLOPT_RETURNTRANSFER, 1); + curl_exec($connection); + $responseStatus = curl_getinfo($connection, CURLINFO_HTTP_CODE); + + return $responseStatus === 200 ? true : false; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index f1a79490b3d..99de6088b19 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -381,84 +381,11 @@ public function testSearchWithFilterWithPageSizeEqualTotalCount() } QUERY; $this->expectException(\Exception::class); - $this->expectExceptionMessage('GraphQL response contains errors: currentPage value 1 specified is greater ' . - 'than the number of pages available.'); + $this->expectExceptionMessage('GraphQL response contains errors: currentPage value 2 specified is greater ' . + 'than the 1 page(s) available'); $this->graphQlQuery($query); } - /** - * The query returns a total_count of 2 records; setting the pageSize = 1 and currentPage2 - * Expected result is to get the second product on the list on the second page - * - * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php - */ - public function testSearchWithFilterPageSizeLessThanCurrentPage() - { - - $query - = <<<QUERY -{ - products( - search : "simple" - filter: - { - special_price:{neq:"null"} - price:{lt:"60"} - or: - { - sku:{like:"%simple%"} - name:{like:"%configurable%"} - } - weight:{eq:"1"} - } - pageSize:1 - currentPage:2 - sort: - { - price:DESC - } - ) - { - items - { - sku - price { - minimalPrice { - amount { - value - currency - } - } - } - name - ... on PhysicalProductInterface { - weight - } - type_id - attribute_set_id - } - total_count - page_info - { - page_size - } - } -} -QUERY; - /** - * @var ProductRepositoryInterface $productRepository - */ - $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - // when pagSize =1 and currentPage = 2, it should have simple2 on first page and simple1 on 2nd page - // since sorting is done on price in the DESC order - $product = $productRepository->get('simple1'); - $filteredProducts = [$product]; - - $response = $this->graphQlQuery($query); - $this->assertEquals(1, $response['products']['total_count']); - $this->assertProductItems($filteredProducts, $response); - } - /** * Requesting for items that match a specific SKU or NAME within a certain price range sorted by Price in ASC order * @@ -549,12 +476,11 @@ public function testQueryProductsInCurrentPageSortedByPriceASC() } /** - * Verify the items in the second page is correct after sorting their name in ASC order + * Verify the items is correct after sorting their name in ASC order * * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testFilterProductsInNextPageSortedByNameASC() + public function testQueryProductsSortedByNameASC() { $query = <<<QUERY @@ -562,44 +488,27 @@ public function testFilterProductsInNextPageSortedByNameASC() products( filter: { - price:{gt: "5", lt: "50"} - or: - { - sku:{eq:"simple1"} - name:{like:"configurable%"} - } + sku:{in:["simple2", "simple1"]} } - pageSize:4 + pageSize:1 currentPage:2 sort: { - name:ASC + name:ASC } ) { items { sku - price { - minimalPrice { - amount { - value - currency - } - } - } name - type_id - ... on PhysicalProductInterface { - weight - } - attribute_set_id - } - total_count - page_info - { + } + total_count + page_info + { page_size - } + current_page + } } } QUERY; @@ -607,13 +516,15 @@ public function testFilterProductsInNextPageSortedByNameASC() * @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $product = $productRepository->get('simple1'); - $filteredProducts = [$product]; + $product = $productRepository->get('simple2'); $response = $this->graphQlQuery($query); - $this->assertEquals(1, $response['products']['total_count']); - $this->assertProductItems($filteredProducts, $response); - $this->assertEquals(4, $response['products']['page_info']['page_size']); + $this->assertEquals(2, $response['products']['total_count']); + $this->assertEquals(['page_size' => 1, 'current_page' => 2], $response['products']['page_info']); + $this->assertEquals( + [['sku' => $product->getSku(), 'name' => $product->getName()]], + $response['products']['items'] + ); } /** @@ -1132,8 +1043,8 @@ public function testQueryPageOutOfBoundException() QUERY; $this->expectException(\Exception::class); - $this->expectExceptionMessage('GraphQL response contains errors: currentPage value 1 specified is greater ' . - 'than the number of pages available.'); + $this->expectExceptionMessage('GraphQL response contains errors: currentPage value 2 specified is greater ' . + 'than the 1 page(s) available.'); $this->graphQlQuery($query); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index 34cebde64d0..7c2cda3a455 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -205,7 +205,9 @@ public function testQueryAllFieldsSimpleProduct() } short_description sku - small_image + small_image { + path + } small_image_label special_from_date special_price @@ -484,7 +486,7 @@ public function testQueryMediaGalleryEntryFieldsSimpleProduct() QUERY; $response = $this->graphQlQuery($query); - + /** * @var ProductRepositoryInterface $productRepository */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductWithDescriptionDirectivesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductWithDescriptionDirectivesTest.php new file mode 100644 index 00000000000..8e9bc4dfa28 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductWithDescriptionDirectivesTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Catalog; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for checking that product fields with directives allowed are rendered correctly + */ +class ProductWithDescriptionDirectivesTest extends GraphQlAbstract +{ + /** + * @var \Magento\TestFramework\ObjectManager + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/Cms/_files/block.php + */ + public function testHtmlDirectivesRendered() + { + $productSku = 'simple'; + $cmsBlockId = 'fixture_block'; + $assertionCmsBlockText = 'Fixture Block Title'; + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + /** @var ProductInterface $product */ + $product = $productRepository->get($productSku, false, null, true); + $product->setDescription('Test: {{block id="' . $cmsBlockId . '"}}'); + $product->setShortDescription('Test: {{block id="' . $cmsBlockId . '"}}'); + $productRepository->save($product); + + $query = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) { + items { + description + short_description + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + + self::assertContains($assertionCmsBlockText, $response['products']['items'][0]['description']); + self::assertNotContains('{{block id', $response['products']['items'][0]['description']); + self::assertContains($assertionCmsBlockText, $response['products']['items'][0]['short_description']); + self::assertNotContains('{{block id', $response['products']['items'][0]['short_description']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/UrlRewritesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/UrlRewritesTest.php new file mode 100644 index 00000000000..c39b32e4bfa --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/UrlRewritesTest.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Catalog; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteDTO; + +/** + * Test of getting URL rewrites data from products + */ +class UrlRewritesTest extends GraphQlAbstract +{ + /** + * + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testProductWithNoCategoriesAssigned() + { + $productSku = 'virtual-product'; + $query + = <<<QUERY +{ + products (filter: {sku: {eq: "{$productSku}"}}) { + items { + name, + sku, + description, + url_rewrites { + url, + parameters { + name, + value + } + } + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get('virtual-product', false, null, true); + + $urlFinder = ObjectManager::getInstance()->get(UrlFinderInterface::class); + + $rewritesCollection = $urlFinder->findAllByData([UrlRewriteDTO::ENTITY_ID => $product->getId()]); + + /* There should be only one rewrite */ + /** @var UrlRewriteDTO $urlRewrite */ + $urlRewrite = current($rewritesCollection); + + $this->assertArrayHasKey('url_rewrites', $response['products']['items'][0]); + $this->assertCount(1, $response['products']['items'][0]['url_rewrites']); + + $this->assertResponseFields( + $response['products']['items'][0]['url_rewrites'][0], + [ + "url" => $urlRewrite->getRequestPath(), + "parameters" => $this->getUrlParameters($urlRewrite->getTargetPath()) + ] + ); + } + + /** + * + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testProductWithOneCategoryAssigned() + { + $productSku = 'simple'; + $query + = <<<QUERY +{ + products (filter: {sku: {eq: "{$productSku}"}}) { + items { + name, + sku, + description, + url_rewrites { + url, + parameters { + name, + value + } + } + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple', false, null, true); + + $urlFinder = ObjectManager::getInstance()->get(UrlFinderInterface::class); + + $rewritesCollection = $urlFinder->findAllByData([UrlRewriteDTO::ENTITY_ID => $product->getId()]); + $rewritesCount = count($rewritesCollection); + + $this->assertArrayHasKey('url_rewrites', $response['products']['items'][0]); + $this->assertCount($rewritesCount, $response['products']['items'][0]['url_rewrites']); + + for ($i = 0; $i < $rewritesCount; $i++) { + $urlRewrite = $rewritesCollection[$i]; + $this->assertResponseFields( + $response['products']['items'][0]['url_rewrites'][$i], + [ + "url" => $urlRewrite->getRequestPath(), + "parameters" => $this->getUrlParameters($urlRewrite->getTargetPath()) + ] + ); + } + } + + /** + * Parses target path and extracts parameters + * + * @param string $targetPath + * @return array + */ + private function getUrlParameters(string $targetPath): array + { + $urlParameters = []; + $targetPathParts = explode('/', trim($targetPath, '/')); + + for ($i = 3; ($i < sizeof($targetPathParts) - 1); $i += 2) { + $urlParameters[] = [ + 'name' => $targetPathParts[$i], + 'value' => $targetPathParts[$i + 1] + ]; + } + + return $urlParameters; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php index 4e49bb63e49..3969c758f12 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/ProductOnlyXLeftInStockTest.php @@ -16,7 +16,6 @@ class ProductOnlyXLeftInStockTest extends GraphQlAbstract */ public function testQueryProductOnlyXLeftInStockDisabled() { - $this->cleanCache(); $productSku = 'simple'; $query = <<<QUERY diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php new file mode 100644 index 00000000000..2caee84ba6f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php @@ -0,0 +1,343 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\CustomerAuthUpdate; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class AccountInformationTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var AccountManagementInterface + */ + private $accountManagement; + + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + /** + * @var CustomerAuthUpdate + */ + private $customerAuthUpdate; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + protected function setUp() + { + parent::setUp(); + + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->accountManagement = Bootstrap::getObjectManager()->get(AccountManagementInterface::class); + $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); + $this->customerAuthUpdate = Bootstrap::getObjectManager()->get(CustomerAuthUpdate::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGetAccountInformation() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +query { + customer { + firstname + lastname + email + } +} +QUERY; + $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + + $this->assertEquals('John', $response['customer']['firstname']); + $this->assertEquals('Smith', $response['customer']['lastname']); + $this->assertEquals($currentEmail, $response['customer']['email']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testGetAccountInformationIfUserIsNotAuthorized() + { + $query = <<<QUERY +query { + customer { + firstname + lastname + email + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The account is locked. + */ + public function testGetAccountInformationIfCustomerIsLocked() + { + $this->lockCustomer(1); + + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +query { + customer { + firstname + lastname + email + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testUpdateAccountInformation() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $newEmail = 'customer_updated@example.com'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + email: "{$newEmail}" + password: "{$currentPassword}" + } + ) { + customer { + firstname + lastname + email + } + } +} +QUERY; + $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + + $this->assertEquals($newFirstname, $response['updateCustomer']['customer']['firstname']); + $this->assertEquals($newLastname, $response['updateCustomer']['customer']['lastname']); + $this->assertEquals($newEmail, $response['updateCustomer']['customer']['email']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage "input" value should be specified + */ + public function testUpdateAccountInformationIfInputDataIsEmpty() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + + } + ) { + customer { + firstname + } + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testUpdateAccountInformationIfUserIsNotAuthorized() + { + $newFirstname = 'Richard'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + firstname: "{$newFirstname}" + } + ) { + customer { + firstname + } + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The account is locked. + */ + public function testUpdateAccountInformationIfCustomerIsLocked() + { + $this->lockCustomer(1); + + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $newFirstname = 'Richard'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + firstname: "{$newFirstname}" + } + ) { + customer { + firstname + } + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage For changing "email" you should provide current "password". + */ + public function testUpdateEmailIfPasswordIsMissed() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $newEmail = 'customer_updated@example.com'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + email: "{$newEmail}" + } + ) { + customer { + firstname + } + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The password doesn't match this account. Verify the password and try again. + */ + public function testUpdateEmailIfPasswordIsInvalid() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $invalidPassword = 'invalid_password'; + $newEmail = 'customer_updated@example.com'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + email: "{$newEmail}" + password: "{$invalidPassword}" + } + ) { + customer { + firstname + } + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/two_customers.php + * @expectedException \Exception + * @expectedExceptionMessage A customer with the same email address already exists in an associated website. + */ + public function testUpdateEmailIfEmailAlreadyExists() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $existedEmail = 'customer_two@example.com'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + email: "{$existedEmail}" + password: "{$currentPassword}" + } + ) { + customer { + firstname + } + } +} +QUERY; + $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * @param int $customerId + * @return void + */ + private function lockCustomer(int $customerId): void + { + $customerSecure = $this->customerRegistry->retrieveSecureData($customerId); + $customerSecure->setLockExpires('2030-12-31 00:00:00'); + $this->customerAuthUpdate->saveAuth($customerId); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php similarity index 84% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php index 88ce7e91d94..9b7e3f28327 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php @@ -14,16 +14,13 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; -class CustomerAuthenticationTest extends GraphQlAbstract +class AddressesTest extends GraphQlAbstract { /** - * Verify customers with valid credentials with a customer bearer token - * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testRegisteredCustomerWithValidCredentials() + public function testGetCustomerWithAddresses() { $query = <<<QUERY @@ -78,39 +75,6 @@ public function testRegisteredCustomerWithValidCredentials() $this->assertCustomerAddressesFields($customer, $response); } - /** - * Verify customer with valid credentials but without the bearer token - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCustomerWithValidCredentialsWithoutToken() - { - $query - = <<<QUERY -{ - customer - { - created_at - group_id - prefix - firstname - middlename - lastname - suffix - email - default_billing - default_shipping - id - } -} -QUERY; - - $this->expectException(\Exception::class); - $this->expectExceptionMessage('GraphQL response contains errors: Current customer' . ' ' . - 'does not have access to the resource "customer"'); - $this->graphQlQuery($query); - } - /** * Verify the all the whitelisted fields for a Customer Object * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php new file mode 100644 index 00000000000..f2451818152 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Framework\Exception\LocalizedException; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class CustomerChangePasswordTest extends GraphQlAbstract +{ + /** + * @var AccountManagementInterface + */ + private $accountManagement; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + protected function setUp() + { + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->accountManagement = Bootstrap::getObjectManager()->get(AccountManagementInterface::class); + $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testChangePassword() + { + $customerEmail = 'customer@example.com'; + $oldCustomerPassword = 'password'; + $newCustomerPassword = 'anotherPassword1'; + + $query = $this->getChangePassQuery($oldCustomerPassword, $newCustomerPassword); + $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); + + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertEquals($customerEmail, $response['changeCustomerPassword']['email']); + + try { + // registry contains the old password hash so needs to be reset + $this->customerRegistry->removeByEmail($customerEmail); + $this->accountManagement->authenticate($customerEmail, $newCustomerPassword); + } catch (LocalizedException $e) { + $this->fail('Password was not changed: ' . $e->getMessage()); + } + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testChangePasswordIfUserIsNotAuthorizedTest() + { + $query = $this->getChangePassQuery('currentpassword', 'newpassword'); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testChangeWeakPassword() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/190'); + $customerEmail = 'customer@example.com'; + $oldCustomerPassword = 'password'; + $newCustomerPassword = 'weakpass'; + + $query = $this->getChangePassQuery($oldCustomerPassword, $newCustomerPassword); + $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); + + $this->expectException(\Exception::class); + $this->expectExceptionMessageRegExp('/Minimum of different classes of characters in password is.*/'); + + $this->graphQlQuery($query, [], '', $headerMap); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The password doesn't match this account. Verify the password and try again. + */ + public function testChangePasswordIfPasswordIsInvalid() + { + $customerEmail = 'customer@example.com'; + $oldCustomerPassword = 'password'; + $newCustomerPassword = 'anotherPassword1'; + $incorrectPassword = 'password-incorrect'; + + $query = $this->getChangePassQuery($incorrectPassword, $newCustomerPassword); + + $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); + $this->graphQlQuery($query, [], '', $headerMap); + } + + private function getChangePassQuery($currentPassword, $newPassword) + { + $query = <<<QUERY +mutation { + changeCustomerPassword( + currentPassword: "$currentPassword", + newPassword: "$newPassword" + ) { + id + email + firstname + lastname + } +} +QUERY; + + return $query; + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php new file mode 100644 index 00000000000..ae28e23a28b --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\TestFramework\TestCase\GraphQlAbstract; +use PHPUnit\Framework\TestResult; + +/** + * Class GenerateCustomerTokenTest + * @package Magento\GraphQl\Customer + */ +class GenerateCustomerTokenTest extends GraphQlAbstract +{ + /** + * Verify customer token with valid credentials + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGenerateCustomerValidToken() + { + $userName = 'customer@example.com'; + $password = 'password'; + + $mutation + = <<<MUTATION +mutation { + generateCustomerToken( + email: "{$userName}" + password: "{$password}" + ) { + token + } +} +MUTATION; + + $response = $this->graphQlQuery($mutation); + $this->assertArrayHasKey('generateCustomerToken', $response); + $this->assertInternalType('array', $response['generateCustomerToken']); + } + + /** + * Verify customer with invalid credentials + */ + public function testGenerateCustomerTokenWithInvalidCredentials() + { + $userName = 'customer@example.com'; + $password = 'bad-password'; + + $mutation + = <<<MUTATION +mutation { + generateCustomerToken( + email: "{$userName}" + password: "{$password}" + ) { + token + } +} +MUTATION; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('GraphQL response contains errors: The account sign-in' . ' ' . + 'was incorrect or your account is disabled temporarily. Please wait and try again later.'); + $this->graphQlQuery($mutation); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php new file mode 100644 index 00000000000..415a81f8cf4 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for revoke customer token mutation + */ +class RevokeCustomerTokenTest extends GraphQlAbstract +{ + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testRevokeCustomerTokenValidCredentials() + { + $query = <<<QUERY + mutation { + revokeCustomerToken + } +QUERY; + + $userName = 'customer@example.com'; + $password = 'password'; + /** @var CustomerTokenServiceInterface $customerTokenService */ + $customerTokenService = ObjectManager::getInstance()->get(CustomerTokenServiceInterface::class); + $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); + + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertTrue($response['revokeCustomerToken']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testRevokeCustomerTokenForGuestCustomer() + { + $query = <<<QUERY + mutation { + revokeCustomerToken + } +QUERY; + $this->graphQlQuery($query, [], ''); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php new file mode 100644 index 00000000000..191ea1ae6b8 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Newsletter\Model\SubscriberFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class SubscriptionStatusTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var SubscriberFactory + */ + private $subscriberFactory; + + protected function setUp() + { + parent::setUp(); + + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->subscriberFactory = Bootstrap::getObjectManager()->get(SubscriberFactory::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGetSubscriptionStatusTest() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +query { + customer { + is_subscribed + } +} +QUERY; + $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->assertFalse($response['customer']['is_subscribed']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testGetSubscriptionStatusIfUserIsNotAuthorizedTest() + { + $query = <<<QUERY +query { + customer { + is_subscribed + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testChangeSubscriptionStatusTest() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<<QUERY +mutation { + updateCustomer( + input: { + is_subscribed: true + } + ) { + customer { + is_subscribed + } + } +} +QUERY; + $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->assertTrue($response['updateCustomer']['customer']['is_subscribed']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testChangeSubscriptionStatuIfUserIsNotAuthorizedTest() + { + $query = <<<QUERY +mutation { + updateCustomer( + input: { + is_subscribed: true + } + ) { + customer { + is_subscribed + } + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + protected function tearDown() + { + parent::tearDown(); + + $this->subscriberFactory->create()->loadByCustomerId(1)->delete(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php new file mode 100644 index 00000000000..1f8ad06a9f8 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php @@ -0,0 +1,225 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for adding/removing shopping cart coupon codes + */ +class CouponTest extends GraphQlAbstract +{ + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var Quote + */ + private $quote; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->create(QuoteResource::class); + $this->quote = $objectManager->create(Quote::class); + $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + */ + public function testApplyCouponToGuestCartWithItems() + { + $couponCode = '2?ds5!2d'; + + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey("applyCouponToCart", $response); + self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + */ + public function testApplyCouponTwice() + { + $couponCode = '2?ds5!2d'; + + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey("applyCouponToCart", $response); + self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); + + self::expectExceptionMessage('A coupon is already applied to the cart. Please remove it to apply another'); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + */ + public function testApplyCouponToCartWithNoItems() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/191'); + $couponCode = '2?ds5!2d'; + + $this->quoteResource->load($this->quote, 'test_order_1', 'reserved_order_id'); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessageRegExp('/Cart doesn\'t contain products/'); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGuestCustomerAttemptToChangeCustomerCart() + { + $couponCode = '2?ds5!2d'; + + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $this->quote->setCustomerId(1); + $this->quoteResource->save($this->quote); + $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + */ + public function testRemoveCoupon() + { + $couponCode = '2?ds5!2d'; + + /* Apply coupon to the quote */ + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); + $this->graphQlQuery($query); + + /* Remove coupon from quote */ + $query = $this->prepareRemoveCouponRequestQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertSame('', $response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testRemoveCouponFromCustomerCartByGuest() + { + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $this->quoteResource->load( + $this->quote, + 'test_order_with_simple_product_without_address', + 'reserved_order_id' + ); + $this->quote->setCustomerId(1); + $this->quoteResource->save($this->quote); + $query = $this->prepareRemoveCouponRequestQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlQuery($query); + } + + /** + * @param string $maskedQuoteId + * @param string $couponCode + * @return string + */ + private function prepareAddCouponRequestQuery(string $maskedQuoteId, string $couponCode): string + { + return <<<QUERY +mutation { + applyCouponToCart(input: {cart_id: "$maskedQuoteId", coupon_code: "$couponCode"}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function prepareRemoveCouponRequestQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + removeCouponFromCart(input: {cart_id: "$maskedQuoteId"}) { + cart { + applied_coupon { + code + } + } + } +} + +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php index 4657a1e763a..a536022e316 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php @@ -25,6 +25,7 @@ class StoreConfigResolverTest extends GraphQlAbstract protected function setUp() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167'); $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php index 0bd24ee7bc8..c70b1631e85 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\Cms\Helper\Page as PageHelper; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; /** * Test the GraphQL endpoint's URLResolver query to verify canonical URL's are correctly returned. @@ -321,6 +324,20 @@ public function testCategoryUrlWithLeadingSlash() */ public function testResolveSlash() { + /** @var \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface */ + $scopeConfigInterface = $this->objectManager->get(ScopeConfigInterface::class); + $homePageIdentifier = $scopeConfigInterface->getValue( + PageHelper::XML_PATH_HOME_PAGE, + ScopeInterface::SCOPE_STORE + ); + /** @var \Magento\Cms\Model\Page $page */ + $page = $this->objectManager->get(\Magento\Cms\Model\Page::class); + $page->load($homePageIdentifier); + $homePageId = $page->getId(); + /** @var \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $urlPathGenerator */ + $urlPathGenerator = $this->objectManager->get(\Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator::class); + /** @param \Magento\Cms\Api\Data\PageInterface $page */ + $targetPath = $urlPathGenerator->getCanonicalUrlPath($page); $query = <<<QUERY { @@ -333,10 +350,9 @@ public function testResolveSlash() } QUERY; $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals(2, $response['urlResolver']['id']); - $this->assertEquals('cms/page/view/page_id/2', $response['urlResolver']['canonical_url']); + $this->assertEquals($homePageId, $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']); $this->assertEquals('CMS_PAGE', $response['urlResolver']['type']); } } diff --git a/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php b/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php index eb04450d526..61e5b764930 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php +++ b/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php @@ -53,7 +53,7 @@ protected function verifyData(array $fixtureData, array $formData, $isStrict = f } $formValue = isset($formData[$key]) ? $formData[$key] : null; if (is_numeric($formValue)) { - $formValue = floatval($formValue); + $formValue = (float)$formValue; } if (null === $formValue) { @@ -118,6 +118,7 @@ protected function sortData(array $data) /** * Sort multidimensional array by paths. + * * Pattern path: key/subKey::sortKey. * Example: * $data = [ @@ -150,7 +151,6 @@ protected function sortData(array $data) * * @param array $data * @param string $path - * @param string $path * @return array * @throws \Exception * diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Handler/Ui/LoginUser.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Handler/Ui/LoginUser.php index 3738f51ef6c..01d8401b22f 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Handler/Ui/LoginUser.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Handler/Ui/LoginUser.php @@ -6,6 +6,7 @@ namespace Magento\Backend\Test\Handler\Ui; +use Magento\Backend\Test\Page\AdminAuthLogin; use Magento\Mtf\Factory\Factory; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Handler\Ui; @@ -29,10 +30,15 @@ public function persist(FixtureInterface $fixture = null) $fixture = Factory::getFixtureFactory()->getMagentoBackendAdminSuperAdmin(); } + /** @var AdminAuthLogin $loginPage */ $loginPage = Factory::getPageFactory()->getAdminAuthLogin(); $loginForm = $loginPage->getLoginBlock(); - $adminHeaderPanel = $loginPage->getHeaderBlock(); + if (!$loginForm->isVisible() && !$adminHeaderPanel->isVisible()) { + //We are currently not in the admin area. + $loginPage->open(); + } + if (!$adminHeaderPanel || !$adminHeaderPanel->isVisible()) { $loginPage->open(); if ($adminHeaderPanel->isVisible()) { 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 9f05a4ade8a..dcc8cce9700 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 @@ -231,7 +231,7 @@ protected function getFieldData(SimpleElement $option) return [ 'options' => [ [ - 'price' => floatval($price), + 'price' => (float)$price, 'max_characters' => $maxCharacters, ], ] @@ -262,7 +262,7 @@ protected function getFileData(SimpleElement $option) return [ 'options' => [ [ - 'price' => floatval($price), + 'price' => (float)$price, 'file_extension' => $this->getOptionNotice($option, 1), 'image_size_x' => preg_replace('/[^0-9]/', '', $this->getOptionNotice($option, 2)), 'image_size_y' => preg_replace('/[^0-9]/', '', $this->getOptionNotice($option, 3)), @@ -344,7 +344,7 @@ protected function getDateData(SimpleElement $option) return [ 'options' => [ [ - 'price' => floatval($price), + 'price' => (float)$price, ], ] ]; diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.php index 5e871bf6d97..707a62a4460 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.php @@ -10,6 +10,7 @@ use Magento\Catalog\Test\Page\Adminhtml\CatalogProductNew; use Magento\Mtf\Fixture\FixtureFactory; use Magento\Mtf\TestCase\Injectable; +use Magento\Downloadable\Test\Block\Adminhtml\Catalog\Product\Edit\Section\Downloadable; /** * Test Creation for ProductTypeSwitchingOnCreation @@ -75,18 +76,49 @@ public function __inject( * * @param string $createProduct * @param string $product + * @param string $actionName * @return array */ - public function test($createProduct, $product) + public function test(string $createProduct, string $product, string $actionName = null): array { // Steps list($fixture, $dataset) = explode('::', $product); $product = $this->fixtureFactory->createByCode($fixture, ['dataset' => $dataset]); $this->catalogProductIndex->open(); $this->catalogProductIndex->getGridPageActionBlock()->addProduct($createProduct); + if ($actionName) { + $this->performAction($actionName); + } $this->catalogProductNew->getProductForm()->fill($product); $this->catalogProductNew->getFormPageActions()->save($product); return ['product' => $product]; } + + /** + * Perform action. + * + * @param string $actionName + * @return void + */ + private function performAction(string $actionName): void + { + if (method_exists(__CLASS__, $actionName)) { + $this->$actionName(); + } + } + + /** + * Clear downloadable product data. + * + * @return void + */ + private function clearDownloadableData(): void + { + $this->catalogProductNew->getProductForm()->openSection('downloadable_information'); + /** @var Downloadable $downloadableInfoTab */ + $downloadableInfoTab = $this->catalogProductNew->getProductForm()->getSection('downloadable_information'); + $downloadableInfoTab->getDownloadableBlock('Links')->clearDownloadableData(); + $downloadableInfoTab->setIsDownloadable('No'); + } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml index f45fbc96a73..460cc29b183 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml @@ -76,7 +76,8 @@ <variation name="ProductTypeSwitchingOnCreationTestVariation9"> <data name="createProduct" xsi:type="string">downloadable</data> <data name="product" xsi:type="string">configurableProduct::not_virtual_for_type_switching</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> + <data name="actionName" xsi:type="string">clearDownloadableData</data> + <data name="issue" xsi:type="string">MSI-1624</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductsInGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php index 7db32337b99..17739f5524e 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php @@ -42,10 +42,10 @@ public function processAssert( $fixtureData = $catalogPriceRule->getData(); //convert discount_amount to float to compare if (isset($formData['discount_amount'])) { - $formData['discount_amount'] = floatval($formData['discount_amount']); + $formData['discount_amount'] = (float)$formData['discount_amount']; } if (isset($fixtureData['discount_amount'])) { - $fixtureData['discount_amount'] = floatval($fixtureData['discount_amount']); + $fixtureData['discount_amount'] = (float)$fixtureData['discount_amount']; } $diff = $this->verifyData($formData, $fixtureData); \PHPUnit\Framework\Assert::assertTrue( diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml index 387e6418ee5..2cb0e287404 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml @@ -125,7 +125,6 @@ <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal"/> - <data name="issue" xsi:type="string">MAGETWO-66737: Magento\Checkout\Test\TestCase\OnePageCheckoutTest with OnePageCheckoutTestVariation3 and 4 is not stable</data> </variation> <variation name="OnePageCheckoutTestVariation5" summary="Guest Checkout using Check/Money Order and Free Shipping with Prices/Taxes Verifications" ticketId="MAGETWO-12412"> <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0</data> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsPage.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsPage.xml index 22d29eae165..1a7adcd531c 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsPage.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/CmsPage.xml @@ -7,6 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/pages.xsd"> <page name="CmsPage" mca="cms/page" module="Magento_Cms"> - <block name="cmsPageBlock" class="Magento\Cms\Test\Block\Page" locator=".page-main" strategy="css selector" /> + <block name="cmsPageBlock" class="Magento\Cms\Test\Block\Page" locator="#maincontent" strategy="css selector" /> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Downloadable.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Downloadable.php index ebc19eec9ad..d78a2d94e76 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Downloadable.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Downloadable.php @@ -104,4 +104,21 @@ public function setFieldsData(array $fields, SimpleElement $element = null) return $this; } + + /** + * Set "Is this downloadable Product?" value. + * + * @param string $downloadable + * @param SimpleElement|null $element + * @return void + */ + public function setIsDownloadable(string $downloadable = 'Yes', SimpleElement $element = null): void + { + $context = $element ?: $this->_rootElement; + $isDownloadable = $context->find($this->isDownloadableProduct); + $value = 'Yes' == $downloadable ? '1' : '0'; + if ($isDownloadable->isVisible() && $isDownloadable->getAttribute('value') != $value) { + $isDownloadable->click(); + } + } } diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php index e5d97e1511e..2033189214e 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php @@ -32,11 +32,7 @@ protected function sortDownloadableArray(array $fields) usort( $fields, function ($row1, $row2) { - if ($row1['sort_order'] == $row2['sort_order']) { - return 0; - } - - return ($row1['sort_order'] < $row2['sort_order']) ? -1 : 1; + return $row1['sort_order'] <=> $row2['sort_order']; } ); diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php index 434c78e55c6..d14d6754b12 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php @@ -148,7 +148,7 @@ protected function prepareLinkData(array $link) 'title' => $link['title'], 'sort_order' => isset($link['sort_order']) ? $link['sort_order'] : 0, 'is_shareable' => $link['is_shareable'], - 'price' => floatval($link['price']), + 'price' => (float)$link['price'], 'number_of_downloads' => isset($link['number_of_downloads']) ? $link['number_of_downloads'] : 0, 'link_type' => $link['type'], 'link_url' => isset($link['link_url']) ? $link['link_url'] : null, diff --git a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php index 745450aa2c0..1236a86e160 100644 --- a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php +++ b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php @@ -52,7 +52,7 @@ protected function prepareSalesResult($salesResult) { $data = []; foreach ($salesResult as $key => $result) { - $data[$key] = floatval($result); + $data[$key] = (float)$result; } return $data; diff --git a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php index 5f435e48229..423ca6dafbd 100644 --- a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php +++ b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php @@ -52,7 +52,7 @@ protected function prepareSalesResult($salesResult) { $data = []; foreach ($salesResult as $key => $result) { - $data[$key] = floatval($result); + $data[$key] = (float)$result; } return $data; diff --git a/dev/tests/functional/utils/log.php b/dev/tests/functional/utils/log.php index b6c19458b10..68a68d4bad6 100644 --- a/dev/tests/functional/utils/log.php +++ b/dev/tests/functional/utils/log.php @@ -4,11 +4,16 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + if (!isset($_GET['name'])) { - throw new \InvalidArgumentException('The name of log file is required for getting logs.'); + throw new \InvalidArgumentException( + 'The name of log file is required for getting logs.' + ); } - $name = urldecode($_GET['name']); -$file = file_get_contents('../../../../var/log/' . $name); +if (preg_match('/\.\.(\\\|\/)/', $name)) { + throw new \InvalidArgumentException('Invalid log file name'); +} -echo serialize($file); +echo serialize(file_get_contents('../../../../var/log' .'/' .$name)); diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php index 4fec36633c9..1bfc928f291 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Application.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php @@ -498,6 +498,7 @@ public function install($cleanup) $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::VAR_DIR][DirectoryList::PATH]); $this->copyAppConfigFiles(); + $this->copyGlobalConfigFile(); $installParams = $this->getInstallCliParams(); @@ -557,6 +558,17 @@ private function copyAppConfigFiles() } } } + + /** + * Copies global configuration file from the tests folder (see TESTS_GLOBAL_CONFIG_FILE) + * + * @return void + */ + private function copyGlobalConfigFile() + { + $targetFile = $this->_configDir . '/config.local.php'; + copy($this->globalConfigFile, $targetFile); + } /** * Gets a list of CLI params for installation diff --git a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php index 543bac2c6b5..b2e0b57bae7 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php +++ b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php @@ -5,10 +5,12 @@ */ namespace Magento\TestFramework\TestCase; +use Magento\Framework\App\Request\Http as HttpRequest; + /** - * A parent class for backend controllers - contains directives for admin user creation and authentication + * A parent class for backend controllers - contains directives for admin user creation and authentication. + * * @SuppressWarnings(PHPMD.NumberOfChildren) - * @SuppressWarnings(PHPMD.numberOfChildren) */ abstract class AbstractBackendController extends \Magento\TestFramework\TestCase\AbstractController { @@ -36,6 +38,16 @@ abstract class AbstractBackendController extends \Magento\TestFramework\TestCase */ protected $uri = null; + /** + * @var string|null + */ + protected $httpMethod; + + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\AuthenticationException + */ protected function setUp() { parent::setUp(); @@ -62,6 +74,9 @@ protected function _getAdminCredentials() ]; } + /** + * @inheritDoc + */ protected function tearDown() { $this->_auth->getAuthStorage()->destroy(['send_expire_cookie' => false]); @@ -86,21 +101,33 @@ public function assertSessionMessages( parent::assertSessionMessages($constraint, $messageType, $messageManagerClass); } + /** + * Test ACL configuration for action working. + */ public function testAclHasAccess() { if ($this->uri === null) { $this->markTestIncomplete('AclHasAccess test is not complete'); } + if ($this->httpMethod) { + $this->getRequest()->setMethod($this->httpMethod); + } $this->dispatch($this->uri); $this->assertNotSame(403, $this->getResponse()->getHttpResponseCode()); $this->assertNotSame(404, $this->getResponse()->getHttpResponseCode()); } + /** + * Test ACL actually denying access. + */ public function testAclNoAccess() { if ($this->resource === null) { $this->markTestIncomplete('Acl test is not complete'); } + if ($this->httpMethod) { + $this->getRequest()->setMethod($this->httpMethod); + } $this->_objectManager->get(\Magento\Framework\Acl\Builder::class) ->getAcl() ->deny(null, $this->resource); diff --git a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php index 1bf6d471fc4..feb9eca0793 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php +++ b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php @@ -10,10 +10,12 @@ namespace Magento\TestFramework\TestCase; use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Message\MessageInterface; use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; use Magento\Theme\Controller\Result\MessagePlugin; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\App\Response\Http as HttpResponse; /** * @SuppressWarnings(PHPMD.NumberOfChildren) @@ -70,6 +72,9 @@ protected function setUp() $this->_objectManager->removeSharedInstance(\Magento\Framework\App\RequestInterface::class); } + /** + * @inheritDoc + */ protected function tearDown() { $this->_request = null; @@ -114,7 +119,7 @@ public function dispatch($uri) /** * Request getter * - * @return \Magento\Framework\App\RequestInterface + * @return \Magento\Framework\App\RequestInterface|HttpRequest */ public function getRequest() { @@ -127,7 +132,7 @@ public function getRequest() /** * Response getter * - * @return \Magento\Framework\App\ResponseInterface + * @return \Magento\Framework\App\ResponseInterface|HttpResponse */ public function getResponse() { @@ -212,13 +217,21 @@ public function assertSessionMessages( $messageManagerClass = \Magento\Framework\Message\Manager::class ) { $this->_assertSessionErrors = false; - + /** @var MessageInterface[]|string[] $messageObjects */ $messages = $this->getMessages($messageType, $messageManagerClass); + /** @var string[] $messages */ + $messagesFiltered = array_map( + function ($message) { + /** @var MessageInterface|string $message */ + return ($message instanceof MessageInterface) ? $message->toString() : $message; + }, + $messages + ); $this->assertThat( - $messages, + $messagesFiltered, $constraint, - 'Session messages do not meet expectations ' . var_export($messages, true) + 'Session messages do not meet expectations ' . var_export($messagesFiltered, true) ); } diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/IndexTest.php index 219fde6e370..d5a48b96081 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/IndexTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Backend\Controller\Adminhtml; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * @magentoAppArea adminhtml * @magentoDbIsolation enabled @@ -45,6 +47,7 @@ public function testLoggedIndexAction() public function testGlobalSearchAction() { $this->getRequest()->setParam('isAjax', 'true'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue('query', 'dummy'); $this->dispatch('backend/admin/index/globalSearch'); diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/UrlRewriteTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/UrlRewriteTest.php index 1185ae9727e..0d48fc8b0f5 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/UrlRewriteTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/UrlRewriteTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Backend\Controller\Adminhtml; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * @magentoAppArea adminhtml */ @@ -20,6 +22,7 @@ public function testSaveActionCmsPage() $page = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Cms\Model\Page::class); $page->load('page_design_blank', 'identifier'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'description' => 'Some URL rewrite description', diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php index 11f49464bbd..a6d03fcc200 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Controller\Adminhtml; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * @magentoAppArea adminhtml */ @@ -27,6 +29,7 @@ public function testSaveAction($inputData, $defaultAttributes, $attributesSaved $store->load('fixturestore', 'code'); $storeId = $store->getId(); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($inputData); $this->getRequest()->setParam('store', $storeId); $this->getRequest()->setParam('id', 2); @@ -75,6 +78,7 @@ public function testSaveAction($inputData, $defaultAttributes, $attributesSaved */ public function testSaveActionFromProductCreationPage($postData) { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/category/save'); @@ -123,6 +127,9 @@ public static function categoryCreatedFromProductCreationPageDataProvider() return [[$postData], [$postData + ['return_session_messages_only' => 1]]]; } + /** + * Test SuggestCategories finds any categories. + */ public function testSuggestCategoriesActionDefaultCategoryFound() { $this->getRequest()->setParam('label_part', 'Default'); @@ -133,6 +140,9 @@ public function testSuggestCategoriesActionDefaultCategoryFound() ); } + /** + * Test SuggestCategories properly processes search by label. + */ public function testSuggestCategoriesActionNoSuggestions() { $this->getRequest()->setParam('label_part', strrev('Default')); @@ -322,8 +332,12 @@ public function saveActionDataProvider() ]; } + /** + * Test validation. + */ public function testSaveActionCategoryWithDangerRequest() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'general' => [ @@ -374,7 +388,8 @@ public function testMoveAction($parentId, $childId, $childUrlKey, $grandChildId, } $this->getRequest() ->setPostValue('id', $grandChildId) - ->setPostValue('pid', $parentId); + ->setPostValue('pid', $parentId) + ->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/category/move'); $jsonResponse = json_decode($this->getResponse()->getBody()); $this->assertNotNull($jsonResponse); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php index cea49d940cb..a2967878402 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Controller\Adminhtml\Product\Action; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * @magentoAppArea adminhtml */ @@ -23,6 +25,7 @@ public function testSaveActionRedirectsSuccessfully() /** @var $session \Magento\Backend\Model\Session */ $session = $objectManager->get(\Magento\Backend\Model\Session::class); $session->setProductIds([1]); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product_action_attribute/save/store/0'); @@ -69,6 +72,7 @@ public function testSaveActionChangeVisibility($attributes) $session = $objectManager->get(\Magento\Backend\Model\Session::class); $session->setProductIds([$product->getId()]); $this->getRequest()->setParam('attributes', $attributes); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product_action_attribute/save/store/0'); /** @var \Magento\Catalog\Model\Category $category */ @@ -89,6 +93,8 @@ public function testSaveActionChangeVisibility($attributes) } /** + * @param array $attributes Request parameter. + * * @covers \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Validate::execute * * @dataProvider validateActionDataProvider diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php index 4261873cc8e..98eb9d3ad4c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php @@ -6,10 +6,13 @@ namespace Magento\Catalog\Controller\Adminhtml\Product; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml * @magentoDbIsolation enabled + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AttributeTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -25,6 +28,7 @@ public function testWrongFrontendInput() 'frontend_input' => 'some_input', ] ); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); @@ -50,6 +54,7 @@ public function testWithPopup() 'popup' => 'true', 'new_attribute_set_name' => 'new_attribute_set', ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); @@ -71,6 +76,7 @@ public function testWithExceptionWhenSaveAttribute() { $postData = $this->_getAttributeData() + ['attribute_id' => 0, 'frontend_input' => 'boolean']; $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); $this->assertContains( @@ -89,6 +95,7 @@ public function testWrongAttributeId() { $postData = $this->_getAttributeData() + ['attribute_id' => 100500]; $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); $this->assertContains( @@ -113,6 +120,7 @@ public function testAttributeWithoutId() 'set' => 4, 'frontend_input' => 'boolean', ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); @@ -135,6 +143,7 @@ public function testWrongAttributeCode() { $postData = $this->_getAttributeData() + ['attribute_code' => '_()&&&?']; $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); $this->assertContains( @@ -159,6 +168,7 @@ public function testWrongAttributeCode() public function testAttributeWithoutEntityTypeId() { $postData = $this->_getAttributeData() + ['attribute_id' => '2', 'new_attribute_set_name' => ' ']; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals(302, $this->getResponse()->getHttpResponseCode()); @@ -174,6 +184,7 @@ public function testAttributeWithoutEntityTypeId() public function testSaveActionApplyToDataSystemAttribute() { $postData = $this->_getAttributeData() + ['attribute_id' => '2']; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $model = $this->_objectManager->create(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); @@ -187,6 +198,7 @@ public function testSaveActionApplyToDataSystemAttribute() public function testSaveActionApplyToDataUserDefinedAttribute() { $postData = $this->_getAttributeData() + ['attribute_id' => '1']; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $model */ @@ -202,6 +214,7 @@ public function testSaveActionApplyToData() { $postData = $this->_getAttributeData() + ['attribute_id' => '3']; unset($postData['apply_to']); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $model = $this->_objectManager->create(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); @@ -221,6 +234,7 @@ public function testSaveActionCleanAttributeLabelCache() $this->assertEquals('predefined string translation', $this->_translate('string to translate')); $string->saveTranslate('string to translate', 'new string translation'); $postData = $this->_getAttributeData() + ['attribute_id' => 1]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/catalog/product_attribute/save'); $this->assertEquals('new string translation', $this->_translate('string to translate')); @@ -293,6 +307,7 @@ public function testLargeOptionsDataSet() $optionsData []= "option[delete][option_{$i}="; } $attributeData['serialized_options'] = json_encode($optionsData); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($attributeData); $this->dispatch('backend/catalog/product_attribute/save'); $entityTypeId = $this->_objectManager->create( diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/DeleteTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/DeleteTest.php index 7c5d4ea48a2..7e034b8b3cb 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/DeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/DeleteTest.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Controller\Adminhtml\Product\Set; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\App\Request\Http as HttpRequest; class DeleteTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -15,7 +16,7 @@ class DeleteTest extends \Magento\TestFramework\TestCase\AbstractBackendControll public function testDeleteById() { $attributeSet = $this->getAttributeSetByName('empty_attribute_set'); - $this->getRequest()->setParam('id', $attributeSet->getId()); + $this->getRequest()->setParam('id', $attributeSet->getId())->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product_set/delete/'); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php index 5b711b2ea74..8ccd426424a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Set/SaveTest.php @@ -9,6 +9,7 @@ use Magento\Eav\Api\Data\AttributeSetInterface; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; class SaveTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -20,6 +21,7 @@ public function testAlreadyExistsExceptionProcessingWhenGroupCodeIsDuplicated() $attributeSet = $this->getAttributeSetByName('attribute_set_test'); $this->assertNotEmpty($attributeSet, 'Attribute set with name "attribute_set_test" is missed'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue('data', json_encode([ 'attribute_set_name' => 'attribute_set_test', 'groups' => [ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/ProductTest.php index 2877ef4fb36..06bbc43e36e 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/ProductTest.php @@ -6,17 +6,20 @@ namespace Magento\Catalog\Controller\Adminhtml; use Magento\Framework\App\Request\DataPersistorInterface; -use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Message\Manager; -use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml */ class ProductTest extends \Magento\TestFramework\TestCase\AbstractBackendController { + /** + * Test calling save with invalid product's ID. + */ public function testSaveActionWithDangerRequest() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue(['product' => ['entity_id' => 15]]); $this->dispatch('backend/catalog/product/save'); $this->assertSessionMessages( @@ -27,6 +30,8 @@ public function testSaveActionWithDangerRequest() } /** + * Test saving existing product and specifying that we want redirect to new product form. + * * @magentoDataFixture Magento/Catalog/_files/product_simple.php */ public function testSaveActionAndNew() @@ -34,6 +39,7 @@ public function testSaveActionAndNew() $this->getRequest()->setPostValue(['back' => 'new']); $repository = $this->_objectManager->create(\Magento\Catalog\Model\ProductRepository::class); $product = $repository->get('simple'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product/save/id/' . $product->getEntityId()); $this->assertRedirect($this->stringStartsWith('http://localhost/index.php/backend/catalog/product/new/')); $this->assertSessionMessages( @@ -43,6 +49,9 @@ public function testSaveActionAndNew() } /** + * Test saving existing product and specifying that + * we want redirect to new product form with saved product's data applied. + * * @magentoDataFixture Magento/Catalog/_files/product_simple.php */ public function testSaveActionAndDuplicate() @@ -50,6 +59,7 @@ public function testSaveActionAndDuplicate() $this->getRequest()->setPostValue(['back' => 'duplicate']); $repository = $this->_objectManager->create(\Magento\Catalog\Model\ProductRepository::class); $product = $repository->get('simple'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product/save/id/' . $product->getEntityId()); $this->assertRedirect($this->stringStartsWith('http://localhost/index.php/backend/catalog/product/edit/')); $this->assertRedirect( @@ -69,6 +79,9 @@ public function testSaveActionAndDuplicate() ); } + /** + * Testing Add Product button showing. + */ public function testIndexAction() { $this->dispatch('backend/catalog/product'); @@ -109,6 +122,8 @@ public function testIndexAction() } /** + * Testing existing product edit page. + * * @magentoDataFixture Magento/Catalog/_files/product_simple.php */ public function testEditAction() @@ -159,11 +174,13 @@ public function testEditAction() public function testSaveActionWithAlreadyExistingUrlKey(array $postData) { $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/catalog/product/save'); /** @var Manager $messageManager */ $messageManager = $this->_objectManager->get(Manager::class); $messages = $messageManager->getMessages(); $errors = $messages->getItemsByType('error'); + $this->assertNotEmpty($errors); $message = array_shift($errors); $this->assertSame('URL key for specified store already exists.', $message->getText()); $this->assertRedirect($this->stringContains('/backend/catalog/product/new')); @@ -231,7 +248,6 @@ public function saveActionWithAlreadyExistingUrlKeyDataProvider() 'thumbnail' => '/m/a//magento_image.jpg.tmp', 'swatch_image' => '/m/a//magento_image.jpg.tmp', ], - 'form_key' => Bootstrap::getObjectManager()->get(FormKey::class)->getFormKey(), ] ] ]; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php index ec2e97f9424..1cb2caace4f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Controller\Product; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoDataFixture Magento/Catalog/controllers/_files/products.php @@ -23,7 +24,7 @@ class CompareTest extends \Magento\TestFramework\TestCase\AbstractController protected $productRepository; /** - * Test setup + * @inheritDoc */ protected function setUp() { @@ -36,6 +37,8 @@ protected function setUp() } /** + * Test adding product to compare list. + * * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testAddAction() @@ -45,6 +48,7 @@ public function testAddAction() /** @var \Magento\Framework\Data\Form\FormKey $formKey */ $formKey = $objectManager->get(\Magento\Framework\Data\Form\FormKey::class); $product = $this->productRepository->get('simple_product_1'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch( sprintf( 'catalog/product_compare/add/product/%s/form_key/%s?nocookie=1', @@ -69,6 +73,8 @@ public function testAddAction() } /** + * Test comparing a product. + * * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testIndexActionAddProducts() @@ -83,12 +89,15 @@ public function testIndexActionAddProducts() } /** + * Test removing a product from compare list. + * * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testRemoveAction() { $this->_requireVisitorWithTwoProducts(); $product = $this->productRepository->get('simple_product_2'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('catalog/product_compare/remove/product/' . $product->getEntityId()); $this->assertSessionMessages( @@ -102,12 +111,15 @@ public function testRemoveAction() } /** + * Test removing a product from compare list of a registered customer. + * * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function testRemoveActionWithSession() { $this->_requireCustomerWithTwoProducts(); $product = $this->productRepository->get('simple_product_1'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('catalog/product_compare/remove/product/' . $product->getEntityId()); $secondProduct = $this->productRepository->get('simple_product_2'); @@ -122,7 +134,7 @@ public function testRemoveActionWithSession() } /** - * testIndexActionDisplay + * Test getting a list of compared product. */ public function testIndexActionDisplay() { @@ -151,12 +163,13 @@ public function testIndexActionDisplay() } /** - * testClearAction + * Test clearing a list of compared products. */ public function testClearAction() { $this->_requireVisitorWithTwoProducts(); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('catalog/product_compare/clear'); $this->assertSessionMessages( @@ -170,12 +183,15 @@ public function testClearAction() } /** + * Test escaping a session message. + * * @magentoDataFixture Magento/Catalog/_files/product_simple_xss.php */ public function testRemoveActionProductNameXss() { $this->_prepareCompareListWithProductNameXss(); $product = $this->productRepository->get('product-with-xss'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('catalog/product_compare/remove/product/' . $product->getEntityId() . '?nocookie=1'); $this->assertSessionMessages( @@ -187,6 +203,9 @@ public function testRemoveActionProductNameXss() } /** + * Preparing compare list. + * + * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _prepareCompareListWithProductNameXss() @@ -212,7 +231,9 @@ protected function _prepareCompareListWithProductNameXss() } /** - * _requireVisitorWithNoProducts + * Preparing compare list. + * + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _requireVisitorWithNoProducts() { @@ -234,6 +255,9 @@ protected function _requireVisitorWithNoProducts() } /** + * Preparing compare list. + * + * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _requireVisitorWithTwoProducts() @@ -269,6 +293,9 @@ protected function _requireVisitorWithTwoProducts() } /** + * Preparing a compare list. + * + * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _requireCustomerWithTwoProducts() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index a1260c0a7b1..c34120404a9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -585,7 +585,7 @@ public function testGetOptions() continue; } foreach ($option->getValues() as $value) { - $this->assertEquals($expectedValue[$value->getSku()], floatval($value->getPrice())); + $this->assertEquals($expectedValue[$value->getSku()], (float)$value->getPrice()); } } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_wrong_url_key.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_wrong_url_key.php new file mode 100644 index 00000000000..5012bc94721 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_wrong_url_key.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple1') + ->setPrice(10) + ->setDescription('Description with <b>html tag</b>') + ->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]) + ->setUrlKey('cuvee-merlot-cabernet-igp-pays-d-oc-frankrijk') + ->save(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product 2') + ->setSku('simple2') + ->setPrice(10) + ->setDescription('Description with <b>html tag</b>') + ->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]) + ->setUrlKey('normal-url') + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_wrong_url_key_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_wrong_url_key_rollback.php new file mode 100644 index 00000000000..778fa7b15df --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_wrong_url_key_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('simple1', false, null, true); + $productRepository->delete($product); + $product = $productRepository->get('simple2', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index c5e704c2434..237d7660da9 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -1566,6 +1566,46 @@ public function testExistingProductWithUrlKeys() } } + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_wrong_url_key.php + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ + public function testAddUpdateProductWithInvalidUrlKeys() : void + { + $products = [ + 'simple1' => 'cuvee-merlot-cabernet-igp-pays-d-oc-frankrijk', + 'simple2' => 'normal-url', + 'simple3' => 'some-wrong-url' + ]; + $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/_files/products_to_import_with_invalid_url_keys.csv', + 'directory' => $directory + ] + ); + + $errors = $this->_model->setParameters( + ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE, 'entity' => 'catalog_product'] + )->setSource( + $source + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + $this->_model->importData(); + + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Api\ProductRepositoryInterface::class + ); + foreach ($products as $productSku => $productUrlKey) { + $this->assertEquals($productUrlKey, $productRepository->get($productSku)->getUrlKey()); + } + } + /** * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php * @magentoDbIsolation disabled diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_invalid_url_keys.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_invalid_url_keys.csv new file mode 100644 index 00000000000..25bb95ab9e0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_invalid_url_keys.csv @@ -0,0 +1,4 @@ +sku,product_type,store_view_code,name,price,attribute_set_code,url_key +simple1,simple,,"simple 1",25,Default,cuvée merlot-cabernet igp pays d'oc frankrijk +simple2,simple,,"simple 2",34,Default,normal-url +simple3,simple,,"simple 3",58,Default,some!wrong'url diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php index 0ac6eb3c040..6105aba9201 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php @@ -170,7 +170,7 @@ private function setMockStockStateResultToQuoteItemOptions($quoteItem, $resultMo * Tests quantity verifications for configurable product. * * @param int $quantity - quantity of configurable option. - * @param string $errorMessage - expected error message. + * @param string $errorMessageRegexp - expected error message regexp. * @return void * @throws CouldNotSaveException * @throws LocalizedException @@ -179,7 +179,7 @@ private function setMockStockStateResultToQuoteItemOptions($quoteItem, $resultMo * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ - public function testConfigurableWithOptions(int $quantity, string $errorMessage): void + public function testConfigurableWithOptions(int $quantity, string $errorMessageRegexp): void { /** @var ProductRepositoryInterface $productRepository */ $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); @@ -217,17 +217,16 @@ public function testConfigurableWithOptions(int $quantity, string $errorMessage) ] ); - if (!empty($errorMessage)) { - $this->expectException(LocalizedException::class); - $this->expectExceptionMessage($errorMessage); - } - - /** @var Quote $cart */ - $cart = $this->objectManager->create(CartInterface::class); - $result = $cart->addProduct($product, $request); + try { + /** @var Quote $cart */ + $cart = $this->objectManager->create(CartInterface::class); + $result = $cart->addProduct($product, $request); - if (empty($errorMessage)) { - self::assertEquals('Configurable Product', $result->getName()); + if (empty($errorMessageRegexp)) { + self::assertEquals('Configurable Product', $result->getName()); + } + } catch (LocalizedException $e) { + self::assertEquals(1, preg_match($errorMessageRegexp, $e->getMessage())); } } @@ -239,18 +238,20 @@ public function testConfigurableWithOptions(int $quantity, string $errorMessage) */ public function quantityDataProvider(): array { + $qtyRegexp = '/You can buy (this product|Configurable OptionOption 1) only in quantities of 500 at a time/'; + return [ [ 'quantity' => 1, - 'error' => 'The fewest you may purchase is 500.' + 'error_regexp' => '/The fewest you may purchase is 500/' ], [ 'quantity' => 501, - 'error' => 'You can buy Configurable OptionOption 1 only in quantities of 500 at a time' + 'error_regexp' => $qtyRegexp ], [ 'quantity' => 1000, - 'error' => '' + 'error_regexp' => '' ], ]; diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/Index/CouponPostTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/Index/CouponPostTest.php index 07141843793..3e99c5cad3c 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/Index/CouponPostTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/Index/CouponPostTest.php @@ -6,6 +6,8 @@ namespace Magento\Checkout\Controller\Cart\Index; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * @magentoDbIsolation enabled */ @@ -26,6 +28,7 @@ public function testExecute() 'remove' => 0, 'coupon_code' => 'test' ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($inputData); $this->dispatch( 'checkout/cart/couponPost/' diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php index 033ce6185da..fc85cc384a3 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php @@ -22,6 +22,7 @@ use Magento\Customer\Model\Session as CustomerSession; use Magento\Sales\Model\ResourceModel\Order\Collection as OrderCollection; use Magento\Sales\Model\ResourceModel\Order\Item\Collection as OrderItemCollection; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -239,6 +240,7 @@ public function testUpdatePostAction() 'update_cart_action' => 'update_qty', 'form_key' => $formKey->getFormKey(), ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); /** @var $customerSession \Magento\Customer\Model\Session */ $customerSession = $this->_objectManager->create(\Magento\Customer\Model\Session::class); @@ -286,7 +288,8 @@ private function getQuote($reservedOrderId) * Gets \Magento\Quote\Model\Quote\Item from \Magento\Quote\Model\Quote by product id * * @param \Magento\Quote\Model\Quote $quote - * @param int $productId + * @param string|int $productId + * * @return \Magento\Quote\Model\Quote\Item|null */ private function _getQuoteItemIdByProductId($quote, $productId) @@ -321,6 +324,7 @@ public function testAddToCartSimpleProduct($area, $expectedPrice) 'isAjax' => 1 ]; \Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea($area); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $quote = $this->_objectManager->create(\Magento\Checkout\Model\Cart::class); @@ -367,6 +371,7 @@ public function testMessageAtAddToCartWithRedirect() ]; \Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('frontend'); $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod('POST'); $this->dispatch('checkout/cart/add'); @@ -402,6 +407,7 @@ public function testMessageAtAddToCartWithoutRedirect() ]; \Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('frontend'); $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod('POST'); $this->dispatch('checkout/cart/add'); diff --git a/dev/tests/integration/testsuite/Magento/Cms/Block/Widget/BlockTest.php b/dev/tests/integration/testsuite/Magento/Cms/Block/Widget/BlockTest.php index 7ee1f569180..266fd66bc3d 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Block/Widget/BlockTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Block/Widget/BlockTest.php @@ -31,5 +31,6 @@ public function testToHtml() $this->assertContains('<a href="http://example.com/', $result); $this->assertContains('<p>Config value: "http://example.com/".</p>', $result); $this->assertContains('<p>Custom variable: "HTML Value".</p>', $result); + $this->assertSame($cmsBlock->getIdentities(), $block->getIdentities()); } } diff --git a/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/ConfigTest.php index 2ad15fbea6c..24b68e804cd 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/ConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Config/Controller/Adminhtml/System/ConfigTest.php @@ -7,12 +7,16 @@ namespace Magento\Config\Controller\Adminhtml\System; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml */ class ConfigTest extends \Magento\TestFramework\TestCase\AbstractBackendController { + /** + * Test Configuration page existing. + */ public function testEditAction() { $this->dispatch('backend/admin/system_config/edit'); @@ -20,6 +24,8 @@ public function testEditAction() } /** + * Test redirect after changing base URL. + * * @magentoAppIsolation enabled * @magentoDbIsolation enabled */ @@ -43,6 +49,8 @@ public function testChangeBaseUrl() )->setParam( 'section', 'web' + )->setMethod( + HttpRequest::METHOD_POST ); $this->dispatch('backend/admin/system_config/save'); @@ -60,7 +68,9 @@ public function testChangeBaseUrl() } /** - * Reset test framework default base url + * Reset test framework default base url. + * + * @param string $defaultHost */ protected function resetBaseUrl($defaultHost) { diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php index 4254a6ce9c7..b71507ae43f 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/Adminhtml/ProductTest.php @@ -9,6 +9,7 @@ use Magento\Framework\Registry; use Magento\TestFramework\ObjectManager; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml @@ -23,6 +24,7 @@ public function testSaveActionAssociatedProductIds() { $associatedProductIds = ['3', '14', '15', '92']; $associatedProductIdsJSON = json_encode($associatedProductIds); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'id' => 1, diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/CartTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/CartTest.php index 4e0b74ba0f9..0d93d3ad4f4 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Controller/CartTest.php @@ -9,6 +9,8 @@ */ namespace Magento\ConfigurableProduct\Controller; +use Magento\Framework\App\Request\Http as HttpRequest; + class CartTest extends \Magento\TestFramework\TestCase\AbstractController { /** @@ -85,6 +87,7 @@ public function testExecuteForConfigurableLastOption() 'remove' => 0, 'coupon_code' => 'test' ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($inputData); $this->dispatch( 'checkout/cart/couponPost/' diff --git a/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php index 2f89cb72b0c..c1255ae9b1a 100644 --- a/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php @@ -6,7 +6,7 @@ namespace Magento\Contact\Controller; -use Zend\Http\Request; +use Magento\Framework\App\Request\Http as HttpRequest; /** * Contact index controller test @@ -14,7 +14,7 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractController { /** - * testPostAction + * Test contacting. */ public function testPostAction() { @@ -24,8 +24,7 @@ public function testPostAction() 'email' => 'user@example.com', 'hideit' => '', ]; - $this->getRequest()->setPostValue($params); - $this->getRequest()->setMethod(Request::METHOD_POST); + $this->getRequest()->setPostValue($params)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('contact/index/post'); $this->assertRedirect($this->stringContains('contact/index')); @@ -38,14 +37,16 @@ public function testPostAction() } /** + * Test validation. + * + * @param array $params For Request. + * @param string $expectedMessage Expected response. + * * @dataProvider dataInvalidPostAction - * @param array $params - * @param string $expectedMessage */ public function testInvalidPostAction($params, $expectedMessage) { - $this->getRequest()->setPostValue($params); - $this->getRequest()->setMethod(Request::METHOD_POST); + $this->getRequest()->setPostValue($params)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('contact/index/post'); $this->assertRedirect($this->stringContains('contact/index')); diff --git a/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRatesTest.php b/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRatesTest.php index c9f2ffad676..fefd1a7b250 100644 --- a/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRatesTest.php +++ b/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/SaveRatesTest.php @@ -5,6 +5,8 @@ */ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currency; +use Magento\Framework\App\Request\Http as HttpRequest; + class SaveRatesTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -43,6 +45,7 @@ public function testSaveAction() $rate = 1.0000; $request = $this->getRequest(); + $request->setMethod(HttpRequest::METHOD_POST); $request->setPostValue( 'rate', [ @@ -75,6 +78,7 @@ public function testSaveWithWarningAction() $rate = '0'; $request = $this->getRequest(); + $request->setMethod(HttpRequest::METHOD_POST); $request->setPostValue( 'rate', [ diff --git a/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/SaveTest.php b/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/SaveTest.php index 5217c3576a5..2929f137be8 100644 --- a/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currencysymbol/SaveTest.php @@ -5,10 +5,16 @@ */ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currencysymbol; +use Magento\Framework\App\Request\Http as HttpRequest; + class SaveTest extends \Magento\TestFramework\TestCase\AbstractBackendController { /** - * Test save action + * Test save action. + * + * @param string $currencyCode + * @param string $inputCurrencySymbol + * @param string $outputCurrencySymbol * * @magentoConfigFixture currency/options/allow USD * @magentoDbIsolation enabled @@ -31,6 +37,7 @@ public function testSaveAction($currencyCode, $inputCurrencySymbol, $outputCurre $currencyCode => $inputCurrencySymbol, ] ); + $request->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/admin/system_currencysymbol/save'); $this->assertRedirect(); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 186d9c8f87d..921fa81fa6f 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -22,6 +22,7 @@ use Magento\TestFramework\Request; use Magento\TestFramework\Response; use Zend\Stdlib\Parameters; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -56,7 +57,7 @@ public function testIndexAction() } /** - * @return void + * Test sign up form displaying. */ public function testCreateAction() { @@ -97,10 +98,8 @@ public function testForgotPasswordEmailMessageWithSpecialCharacters() { $email = 'customer@example.com'; - $this->getRequest() - ->setPostValue([ - 'email' => $email, - ]); + $this->getRequest()->setPostValue(['email' => $email]); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('customer/account/forgotPasswordPost'); $this->assertRedirect($this->stringContains('customer/account/')); @@ -377,10 +376,8 @@ public function testForgotPasswordPostAction() { $email = 'customer@example.com'; - $this->getRequest() - ->setPostValue([ - 'email' => $email, - ]); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue(['email' => $email]); $this->dispatch('customer/account/forgotPasswordPost'); $this->assertRedirect($this->stringContains('customer/account/')); @@ -400,6 +397,7 @@ public function testForgotPasswordPostAction() */ public function testForgotPasswordPostWithBadEmailAction() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest() ->setPostValue([ 'email' => 'bad@email', diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php index 32a622d4aa6..92e104496fe 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php @@ -9,6 +9,7 @@ use Magento\Customer\Model\CustomerRegistry; use Magento\Framework\Data\Form\FormKey; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; class AddressTest extends \Magento\TestFramework\TestCase\AbstractController { @@ -19,7 +20,7 @@ class AddressTest extends \Magento\TestFramework\TestCase\AbstractController private $formKey; /** - * {@inheritDoc} + * @inheritDoc */ protected function setUp() { @@ -168,7 +169,7 @@ public function testFailedFormPostAction() public function testDeleteAction() { $this->getRequest()->setParam('id', 1); - $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); + $this->getRequest()->setParam('form_key', $this->formKey->getFormKey())->setMethod(HttpRequest::METHOD_POST); // we are overwriting the address coming from the fixture $this->dispatch('customer/address/delete'); @@ -186,7 +187,7 @@ public function testDeleteAction() public function testWrongAddressDeleteAction() { $this->getRequest()->setParam('id', 555); - $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); + $this->getRequest()->setParam('form_key', $this->formKey->getFormKey())->setMethod(HttpRequest::METHOD_POST); // we are overwriting the address coming from the fixture $this->dispatch('customer/address/delete'); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/GroupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/GroupTest.php index 80b11a920e3..db1cc4995e6 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/GroupTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/GroupTest.php @@ -7,6 +7,7 @@ use Magento\Framework\Message\MessageInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml @@ -25,6 +26,11 @@ class GroupTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle /** @var \Magento\Customer\Api\GroupRepositoryInterface */ private $groupRepository; + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\AuthenticationException + */ public function setUp() { parent::setUp(); @@ -33,12 +39,18 @@ public function setUp() $this->groupRepository = $objectManager->get(\Magento\Customer\Api\GroupRepositoryInterface::class); } + /** + * @inheritDoc + */ public function tearDown() { parent::tearDown(); //$this->session->unsCustomerGroupData(); } + /** + * Test new group form. + */ public function testNewActionNoCustomerGroupDataInSession() { $this->dispatch('backend/customer/group/new'); @@ -49,6 +61,9 @@ public function testNewActionNoCustomerGroupDataInSession() $this->assertContains($expected, $responseBody); } + /** + * Test form filling with data in session. + */ public function testNewActionWithCustomerGroupDataInSession() { /** @var \Magento\Customer\Api\Data\GroupInterfaceFactory $customerGroupFactory */ @@ -76,21 +91,27 @@ public function testNewActionWithCustomerGroupDataInSession() } /** + * Test calling delete without an ID. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testDeleteActionNoGroupId() { + $this->getRequest()->setMethod(\Magento\Framework\App\Request\Http::METHOD_POST); $this->dispatch('backend/customer/group/delete'); $this->assertRedirect($this->stringStartsWith(self::BASE_CONTROLLER_URL)); } /** + * Test deleting a group. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testDeleteActionExistingGroup() { $groupId = $this->findGroupIdWithCode(self::CUSTOMER_GROUP_CODE); $this->getRequest()->setParam('id', $groupId); + $this->getRequest()->setMethod(\Magento\Framework\App\Request\Http::METHOD_POST); $this->dispatch('backend/customer/group/delete'); /** @@ -104,11 +125,14 @@ public function testDeleteActionExistingGroup() } /** + * Tet deleting with wrong ID. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testDeleteActionNonExistingGroupId() { $this->getRequest()->setParam('id', 10000); + $this->getRequest()->setMethod(\Magento\Framework\App\Request\Http::METHOD_POST); $this->dispatch('backend/customer/group/delete'); /** @@ -122,6 +146,8 @@ public function testDeleteActionNonExistingGroupId() } /** + * Test saving a valid group. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testSaveActionExistingGroup() @@ -130,6 +156,7 @@ public function testSaveActionExistingGroup() $this->getRequest()->setParam('tax_class', self::TAX_CLASS_ID); $this->getRequest()->setParam('id', $groupId); $this->getRequest()->setParam('code', self::CUSTOMER_GROUP_CODE); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/group/save'); @@ -161,9 +188,13 @@ public function testSaveActionExistingGroup() ); } + /** + * Test saving an invalid group. + */ public function testSaveActionCreateNewGroupWithoutCode() { $this->getRequest()->setParam('tax_class', self::TAX_CLASS_ID); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/group/save'); @@ -173,19 +204,26 @@ public function testSaveActionCreateNewGroupWithoutCode() ); } + /** + * Test saving an empty group. + */ public function testSaveActionForwardNewCreateNewGroup() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/group/save'); $responseBody = $this->getResponse()->getBody(); $this->assertRegExp('/<h1 class\="page-title">\s*New Customer Group\s*<\/h1>/', $responseBody); } /** + * Test saving an existing group. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testSaveActionForwardNewEditExistingGroup() { $groupId = $this->findGroupIdWithCode(self::CUSTOMER_GROUP_CODE); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('id', $groupId); $this->dispatch('backend/customer/group/save'); @@ -193,10 +231,14 @@ public function testSaveActionForwardNewEditExistingGroup() $this->assertRegExp('/<h1 class\="page-title">\s*' . self::CUSTOMER_GROUP_CODE . '\s*<\/h1>/', $responseBody); } + /** + * Test using an invalid ID. + */ public function testSaveActionNonExistingGroupId() { $this->getRequest()->setParam('id', 10000); $this->getRequest()->setParam('tax_class', self::TAX_CLASS_ID); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/group/save'); @@ -211,6 +253,8 @@ public function testSaveActionNonExistingGroupId() } /** + * Test using existing code. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testSaveActionNewGroupWithExistingGroupCode() @@ -220,6 +264,7 @@ public function testSaveActionNewGroupWithExistingGroupCode() $this->getRequest()->setParam('tax_class', self::TAX_CLASS_ID); $this->getRequest()->setParam('code', self::CUSTOMER_GROUP_CODE); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/group/save'); @@ -232,6 +277,8 @@ public function testSaveActionNewGroupWithExistingGroupCode() } /** + * Test saving an invalid group. + * * @magentoDataFixture Magento/Customer/_files/customer_group.php */ public function testSaveActionNewGroupWithoutGroupCode() @@ -240,6 +287,7 @@ public function testSaveActionNewGroupWithoutGroupCode() $originalCode = $this->groupRepository->getById($groupId)->getCode(); $this->getRequest()->setParam('tax_class', self::TAX_CLASS_ID); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/group/save'); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php index 8ee12fa1aa8..761064ed61f 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php @@ -9,6 +9,7 @@ use Magento\Backend\Model\Session; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\Message\MessageInterface; use Magento\TestFramework\Helper\Bootstrap; @@ -31,12 +32,20 @@ class MassAssignGroupTest extends AbstractBackendController */ protected $customerRepository; + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\AuthenticationException + */ protected function setUp() { parent::setUp(); $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); } + /** + * @inheritDoc + */ protected function tearDown() { /** @@ -69,7 +78,8 @@ public function testMassAssignGroupAction() 'selected' => [$customer->getId()] ]; - $this->getRequest()->setParams($params); + $this->getRequest()->setParams($params) + ->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( self::equalTo(['A total of 1 record(s) were updated.']), @@ -103,7 +113,8 @@ public function testLargeGroupMassAssignGroupAction() 'selected' => $ids, ]; - $this->getRequest()->setParams($params); + $this->getRequest()->setParams($params) + ->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( self::equalTo(['A total of 5 record(s) were updated.']), @@ -124,11 +135,9 @@ public function testLargeGroupMassAssignGroupAction() */ public function testMassAssignGroupActionNoCustomerIds() { - $params = [ - 'group' => 0, - 'namespace' => 'customer_listing', + $params = ['group'=> 0,'namespace'=> 'customer_listing', ]; - $this->getRequest()->setParams($params); + $this->getRequest()->setParams($params)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( $this->equalTo(['An item needs to be selected. Select and try again.']), diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php index 940f7c84325..eea94bb3f98 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\Constraint\Constraint; use Magento\Framework\Message\MessageInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\TestFramework\TestCase\AbstractBackendController; /** @@ -32,12 +33,20 @@ class MassDeleteTest extends AbstractBackendController */ private $baseControllerUrl = 'http://localhost/index.php/backend/customer/index/index'; + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\AuthenticationException + */ protected function setUp() { parent::setUp(); $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); } + /** + * @inheritDoc + */ protected function tearDown() { /** @@ -106,7 +115,7 @@ private function massDeleteAssertions($ids, Constraint $constraint, $messageType 'namespace' => 'customer_listing', ]; - $this->getRequest()->setParams($requestData); + $this->getRequest()->setParams($requestData)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/massDelete'); $this->assertSessionMessages( $constraint, diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php index b5ca783d68c..eaaba639d49 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * ResetPassword controller test. * @@ -32,7 +34,7 @@ public function testResetPasswordSuccess() $this->passwordResetRequestEventCreate( \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST ); - $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->getRequest()->setPostValue(['customer_id' => '1'])->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/resetPassword'); $this->assertSessionMessages( $this->equalTo(['The customer will receive an email with a link to reset password.']), @@ -55,7 +57,7 @@ public function testResetPasswordMinTimeError() $this->passwordResetRequestEventCreate( \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST ); - $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->getRequest()->setPostValue(['customer_id' => '1'])->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/resetPassword'); $this->assertSessionMessages( $this->equalTo(['The customer will receive an email with a link to reset password.']), @@ -78,7 +80,7 @@ public function testResetPasswordLimitError() $this->passwordResetRequestEventCreate( \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST ); - $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->getRequest()->setPostValue(['customer_id' => '1'])->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/resetPassword'); $this->assertSessionMessages( $this->equalTo(['The customer will receive an email with a link to reset password.']), @@ -103,7 +105,7 @@ public function testResetPasswordWithSecurityViolationException() $this->passwordResetRequestEventCreate( \Magento\Security\Model\PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST ); - $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->getRequest()->setPostValue(['customer_id' => '1'])->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/resetPassword'); $this->assertSessionMessages( $this->equalTo(['The customer will receive an email with a link to reset password.']), diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 6448816c934..4262967aa8d 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -12,6 +12,7 @@ use Magento\Customer\Controller\RegistryConstants; use Magento\Customer\Model\EmailNotification; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml @@ -45,7 +46,7 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle protected $objectManager; /** - * {@inheritDoc} + * @inheritDoc */ protected function setUp() { @@ -71,7 +72,7 @@ protected function setUp() } /** - * {@inheritDoc} + * @inheritDoc */ protected function tearDown() { @@ -91,7 +92,7 @@ protected function tearDown() */ public function testSaveActionWithEmptyPostData() { - $this->getRequest()->setPostValue([]); + $this->getRequest()->setPostValue([])->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/save'); $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); } @@ -102,7 +103,7 @@ public function testSaveActionWithEmptyPostData() public function testSaveActionWithInvalidFormData() { $post = ['account' => ['middlename' => 'test middlename', 'group_id' => 1]]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/save'); /** * Check that errors was generated and set to session @@ -111,13 +112,13 @@ public function testSaveActionWithInvalidFormData() $this->logicalNot($this->isEmpty()), \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); + /** @var \Magento\Backend\Model\Session $session */ + $session = $this->objectManager->get(\Magento\Backend\Model\Session::class); /** * Check that customer data were set to session */ - $this->assertEquals( - $post, - $this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerFormData() - ); + $this->assertNotEmpty($session->getCustomerFormData()); + $this->assertArraySubset($post, $session->getCustomerFormData()); $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new')); } @@ -138,7 +139,7 @@ public function testSaveActionWithInvalidCustomerAddressData() ], 'address' => ['_item1' => []], ]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/save'); /** * Check that errors was generated and set to session @@ -150,7 +151,7 @@ public function testSaveActionWithInvalidCustomerAddressData() /** * Check that customer data were set to session */ - $this->assertEquals( + $this->assertArraySubset( $post, $this->objectManager->get(\Magento\Backend\Model\Session::class)->getCustomerFormData() ); @@ -187,7 +188,7 @@ public function testSaveActionWithValidCustomerDataAndValidAddressData() ], ], ]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('back', '1'); // Emulate setting customer data to session in editAction @@ -299,7 +300,7 @@ public function testSaveActionExistingCustomerAndExistingAddressData() ], 'subscription' => '', ]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('id', 1); $this->dispatch('backend/customer/index/save'); @@ -365,7 +366,7 @@ public function testSaveActionExistingCustomerUnsubscribeNewsletter() ], 'subscription' => '0' ]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('id', 1); $this->dispatch('backend/customer/index/save'); @@ -426,7 +427,7 @@ public function testSaveActionExistingCustomerChangeEmail() 'default_billing' => 1, ] ]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('id', 1); $this->dispatch('backend/customer/index/save'); @@ -473,7 +474,7 @@ public function testInlineEditChangeEmail() ] ]; $this->getRequest()->setParam('ajax', true)->setParam('isAjax', true); - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('id', 1); $this->dispatch('backend/customer/index/inlineEdit'); @@ -499,7 +500,7 @@ public function testSaveActionCoreException() 'password' => 'password', ], ]; - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/save'); /* * Check that error message is set @@ -508,7 +509,7 @@ public function testSaveActionCoreException() $this->equalTo(['A customer with the same email address already exists in an associated website.']), \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); - $this->assertEquals( + $this->assertArraySubset( $post, Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getCustomerFormData() ); @@ -529,9 +530,9 @@ public function testEditAction() } /** - * Tests new action + * Test new customer form page. */ - public function testNewAction(): void + public function testNewAction() { $this->dispatch('backend/customer/index/edit'); $body = $this->getResponse()->getBody(); @@ -666,7 +667,7 @@ public function testValidateCustomerWithAddressSuccess() /** * set customer data */ - $this->getRequest()->setParams($customerData); + $this->getRequest()->setParams($customerData)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/validate'); $body = $this->getResponse()->getBody(); @@ -720,7 +721,7 @@ public function testValidateCustomerWithAddressFailure() /** * set customer data */ - $this->getRequest()->setPostValue($customerData); + $this->getRequest()->setPostValue($customerData)->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/validate'); $body = $this->getResponse()->getBody(); @@ -737,6 +738,7 @@ public function testValidateCustomerWithAddressFailure() public function testResetPasswordActionNoCustomerId() { // No customer ID in post, will just get redirected to base + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/resetPassword'); $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); } @@ -747,6 +749,7 @@ public function testResetPasswordActionNoCustomerId() public function testResetPasswordActionBadCustomerId() { // Bad customer ID in post, will just get redirected to base + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue(['customer_id' => '789']); $this->dispatch('backend/customer/index/resetPassword'); $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); @@ -758,6 +761,7 @@ public function testResetPasswordActionBadCustomerId() public function testResetPasswordActionSuccess() { $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/resetPassword'); $this->assertSessionMessages( $this->equalTo(['The customer will receive an email with a link to reset password.']), diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/two_customers_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/two_customers_rollback.php new file mode 100644 index 00000000000..5beb9e654de --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/two_customers_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Fixture for Customer List method. + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require 'customer_rollback.php'; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var \Magento\Framework\Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); +try { + $customerRepository->deleteById(2); +} catch (NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/Controller/Adminhtml/Downloadable/FileTest.php b/dev/tests/integration/testsuite/Magento/Downloadable/Controller/Adminhtml/Downloadable/FileTest.php index 3f13ce7f41e..60c2a41fae4 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/Controller/Adminhtml/Downloadable/FileTest.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/Controller/Adminhtml/Downloadable/FileTest.php @@ -37,6 +37,7 @@ public function testUploadAction() ], ]; + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/admin/downloadable_file/upload/type/samples'); $body = $this->getResponse()->getBody(); $result = Bootstrap::getObjectManager()->get(Json::class)->unserialize($body); @@ -64,6 +65,7 @@ public function testUploadProhibitedExtensions($fileName) ], ]; + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/admin/downloadable_file/upload/type/samples'); $body = $this->getResponse()->getBody(); $result = Bootstrap::getObjectManager()->get(Json::class)->unserialize($body); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/Adminhtml/Category/SaveTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/Adminhtml/Category/SaveTest.php index b37392fff76..fcd8226aec5 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/Adminhtml/Category/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/Adminhtml/Category/SaveTest.php @@ -80,6 +80,7 @@ public function testExecute() ]; $this->getRequest()->setPostValue($inputData); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/catalog/category/save'); $this->assertSessionMessages( self::equalTo(['You saved the category.']), diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/FrontControllerTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/FrontControllerTest.php index 431c7054308..89240f4ab32 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/FrontControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/FrontControllerTest.php @@ -5,11 +5,21 @@ */ namespace Magento\Framework\App; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\Request\ValidatorInterface; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Phrase; +use PHPUnit\Framework\TestCase; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Controller\ResultInterface; +use Magento\TestFramework\Helper\Bootstrap; + /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @magentoAppArea frontend */ -class FrontControllerTest extends \PHPUnit\Framework\TestCase +class FrontControllerTest extends TestCase { /** * @var \Magento\Framework\ObjectManagerInterface @@ -17,28 +27,94 @@ class FrontControllerTest extends \PHPUnit\Framework\TestCase protected $_objectManager; /** - * @var \Magento\Framework\App\FrontController + * @var FrontController */ protected $_model; + /** + * @var ValidatorInterface + */ + private $fakeRequestValidator; + + /** + * @return ValidatorInterface + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + private function createRequestValidator(): ValidatorInterface + { + if (!$this->fakeRequestValidator) { + $this->fakeRequestValidator = new class implements ValidatorInterface { + /** + * @var bool + */ + public $valid; + + /** + * @inheritDoc + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void { + if (!$this->valid) { + throw new InvalidRequestException(new NotFoundException(new Phrase('Not found'))); + } + } + }; + } + + return $this->fakeRequestValidator; + } + + /** + * @inheritDoc + */ protected function setUp() { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->_model = $this->_objectManager->create(\Magento\Framework\App\FrontController::class); + $this->_objectManager = Bootstrap::getObjectManager(); + $this->_model = $this->_objectManager->create( + FrontController::class, + ['requestValidator' => $this->createRequestValidator()] + ); } + /** + * Test dispatching an empty action. + */ public function testDispatch() { - if (!\Magento\TestFramework\Helper\Bootstrap::canTestHeaders()) { + if (!Bootstrap::canTestHeaders()) { + $this->markTestSkipped('Can\'t test dispatch process without sending headers'); + } + $this->fakeRequestValidator->valid = true; + $_SERVER['HTTP_HOST'] = 'localhost'; + $this->_objectManager->get(State::class)->setAreaCode('frontend'); + $request = $this->_objectManager->get(HttpRequest::class); + /* empty action */ + $request->setRequestUri('core/index/index'); + $this->assertInstanceOf( + ResultInterface::class, + $this->_model->dispatch($request) + ); + } + + /** + * Test request validator invalidating given request. + */ + public function testInvalidRequest() + { + if (!Bootstrap::canTestHeaders()) { $this->markTestSkipped('Can\'t test dispatch process without sending headers'); } + $this->fakeRequestValidator->valid = false; $_SERVER['HTTP_HOST'] = 'localhost'; - $this->_objectManager->get(\Magento\Framework\App\State::class)->setAreaCode('frontend'); - $request = $this->_objectManager->get(\Magento\Framework\App\Request\Http::class); + $this->_objectManager->get(State::class)->setAreaCode('frontend'); + $request = $this->_objectManager->get(HttpRequest::class); /* empty action */ $request->setRequestUri('core/index/index'); $this->assertInstanceOf( - \Magento\Framework\Controller\ResultInterface::class, + ResultInterface::class, $this->_model->dispatch($request) ); } diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Request/HttpMethodValidatorTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Request/HttpMethodValidatorTest.php new file mode 100644 index 00000000000..74aa5b8bfed --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Request/HttpMethodValidatorTest.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\RequestInterface; +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; + +class HttpMethodValidatorTest extends TestCase +{ + /** + * @var HttpMethodValidator + */ + private $validator; + + /** + * @var HttpRequest + */ + private $request; + + /** + * @var HttpMethodMap + */ + private $map; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->validator = $objectManager->get(HttpMethodValidator::class); + $this->request = $objectManager->get(RequestInterface::class); + if (!$this->request instanceof HttpRequest) { + throw new \RuntimeException('We need HTTP request'); + } + $this->map = $objectManager->get(HttpMethodMap::class); + } + + /** + * @return array + */ + private function getMap(): array + { + $map = $this->map->getMap(); + if (count($map) < 2) { + throw new \RuntimeException( + 'We need at least 2 HTTP methods allowed' + ); + } + + $sorted = []; + foreach ($map as $method => $interface) { + $sorted[] = ['method' => $method, 'interface' => $interface]; + } + + return $sorted; + } + + /** + * Test positive case. + * + * @throws InvalidRequestException + */ + public function testAllowed() + { + $map = $this->getMap(); + + $action1 = $this->getMockForAbstractClass($map[0]['interface']); + $this->request->setMethod($map[0]['method']); + $this->validator->validate($this->request, $action1); + + $action2 = $this->getMockForAbstractClass(ActionInterface::class); + $this->validator->validate($this->request, $action2); + } + + /** + * Test negative case. + * + * @expectedException \Magento\Framework\App\Request\InvalidRequestException + */ + public function testNotAllowedMethod() + { + $this->request->setMethod('method' .rand(0, 1000)); + $action = $this->getMockForAbstractClass(ActionInterface::class); + + $this->validator->validate($this->request, $action); + } + + /** + * @expectedException \Magento\Framework\App\Request\InvalidRequestException + */ + public function testRestrictedMethod() + { + $map = $this->getMap(); + + $this->request->setMethod($map[1]['method']); + $action = $this->getMockForAbstractClass($map[0]['interface']); + + $this->validator->validate($this->request, $action); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php index 62a91b913fb..e5bce7c52db 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php @@ -7,6 +7,9 @@ use Zend\Code\Generator\ClassGenerator; +/** + * Class SourceClassWithNamespace + */ class SourceClassWithNamespace extends ParentClassWithNamespace { /** @@ -96,15 +99,23 @@ private function _privateChildMethod( ) { } + /** + * Test method + */ public function publicChildWithoutParameters() { } + /** + * Test method + */ public static function publicChildStatic() { } /** + * Test method + * * @SuppressWarnings(PHPMD.FinalImplementation) Suppressed as is a fixture but not a real code */ final public function publicChildFinal() @@ -112,6 +123,8 @@ final public function publicChildFinal() } /** + * Test method + * * @param mixed $arg1 * @param string $arg2 * @param int|null $arg3 @@ -130,6 +143,8 @@ public function public71( } /** + * Test method + * * @param \DateTime|null $arg1 * @param mixed $arg2 * @@ -140,4 +155,16 @@ public function public71( public function public71Another(?\DateTime $arg1, $arg2 = false): ?string { } + + /** + * Test method + * + * @param bool $arg + * @return SourceClassWithNamespace + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function publicWithSelf($arg = false): self + { + } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample index 2a240bb685f..83191f2d1b0 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample @@ -80,6 +80,19 @@ class Interceptor extends \Magento\Framework\Code\GeneratorTest\SourceClassWithN } } + /** + * {@inheritdoc} + */ + public function publicWithSelf($arg = false) : \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespace + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'publicWithSelf'); + if (!$pluginInfo) { + return parent::publicWithSelf($arg); + } else { + return $this->___callPlugins('publicWithSelf', func_get_args(), $pluginInfo); + } + } + /** * {@inheritdoc} */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample index 0aa086c0ab8..359854f2d48 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample @@ -130,6 +130,14 @@ class Proxy extends \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespa return $this->_getSubject()->public71Another($arg1, $arg2); } + /** + * {@inheritdoc} + */ + public function publicWithSelf($arg = false) : \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespace + { + return $this->_getSubject()->publicWithSelf($arg); + } + /** * {@inheritdoc} */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php b/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php index bfe8264d7cc..211b28617cb 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php @@ -24,7 +24,7 @@ public function testRemove() [ 'command' => 'remove', 'packages' => ['magento/package-a', 'magento/package-b'], - '--no-update' => true, + '--no-update-with-dependencies' => true, ] ); $composerAppFactory->expects($this->once()) diff --git a/dev/tests/integration/testsuite/Magento/GiftMessage/Observer/SalesEventQuoteMergeTest.php b/dev/tests/integration/testsuite/Magento/GiftMessage/Observer/SalesEventQuoteMergeTest.php new file mode 100644 index 00000000000..0902c35568e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GiftMessage/Observer/SalesEventQuoteMergeTest.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GiftMessage\Observer; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Event\ManagerInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\Quote; + +class SalesEventQuoteMergeTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoAppArea frontend + */ + public function testQuoteMerge() + { + $giftMessageId = 6; + $objectManager = Bootstrap::getObjectManager(); + $eventManager = $objectManager->get(ManagerInterface::class); + /** @var Quote $sourceQuote */ + $sourceQuote = $objectManager->create(QuoteFactory::class)->create(); + $targetQuote = clone($sourceQuote); + $sourceQuote->setGiftMessageId($giftMessageId); + + $eventManager->dispatch( + 'sales_quote_merge_after', + [ + 'quote' => $targetQuote, + 'source' => $sourceQuote + ] + ); + + self::assertEquals($giftMessageId, $targetQuote->getGiftMessageId()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Integration/Controller/Adminhtml/IntegrationTest.php b/dev/tests/integration/testsuite/Magento/Integration/Controller/Adminhtml/IntegrationTest.php index 8011873577d..4da0c12c608 100644 --- a/dev/tests/integration/testsuite/Magento/Integration/Controller/Adminhtml/IntegrationTest.php +++ b/dev/tests/integration/testsuite/Magento/Integration/Controller/Adminhtml/IntegrationTest.php @@ -7,6 +7,7 @@ namespace Magento\Integration\Controller\Adminhtml; use Magento\TestFramework\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; /** * \Magento\Integration\Controller\Adminhtml\Integration @@ -20,6 +21,9 @@ class IntegrationTest extends \Magento\TestFramework\TestCase\AbstractBackendCon /** @var \Magento\Integration\Model\Integration */ private $_integration; + /** + * @inheritDoc + */ protected function setUp() { parent::setUp(); @@ -29,6 +33,9 @@ protected function setUp() $this->_integration = $integration->load('Fixture Integration', 'name'); } + /** + * Test view page. + */ public function testIndexAction() { $this->dispatch('backend/admin/integration/index'); @@ -44,6 +51,9 @@ public function testIndexAction() ); } + /** + * Test creation form. + */ public function testNewAction() { $this->dispatch('backend/admin/integration/new'); @@ -61,6 +71,9 @@ public function testNewAction() ); } + /** + * Test update form. + */ public function testEditAction() { $integrationId = $this->_integration->getId(); @@ -88,12 +101,16 @@ public function testEditAction() ); } + /** + * Test saving. + */ public function testSaveActionUpdateIntegration() { $integrationId = $this->_integration->getId(); $integrationName = $this->_integration->getName(); $this->getRequest()->setParam('id', $integrationId); $url = 'http://magento.ll/endpoint_url'; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'name' => $integrationName, @@ -111,10 +128,14 @@ public function testSaveActionUpdateIntegration() $this->assertRedirect($this->stringContains('backend/admin/integration/index/')); } + /** + * Test saving. + */ public function testSaveActionNewIntegration() { $url = 'http://magento.ll/endpoint_url'; $integrationName = md5(rand()); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'name' => $integrationName, diff --git a/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php b/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php index cc2841f1acf..d72a1359dfa 100644 --- a/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php +++ b/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php @@ -9,6 +9,7 @@ 'endpoint_url' => 'http://example.com/endpoint1', 'identity_link_url' => 'http://www.example.com/identity1', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Customer::customer', 'Magento_Customer::manage', 'Magento_Sales::sales', @@ -26,6 +27,7 @@ 'endpoint_url' => 'http://example.com/integration2', 'identity_link_url' => 'http://www.example.com/identity2', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Sales::sales', 'Magento_Sales::sales_operation', 'Magento_Sales::sales_order', @@ -40,6 +42,7 @@ 'TestIntegration3' => [ 'email' => 'test-integration3@example.com', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Sales::sales', 'Magento_Sales::sales_operation', 'Magento_Sales::sales_order', diff --git a/dev/tests/integration/testsuite/Magento/Integration/Model/ConfigBasedIntegrationManagerTest.php b/dev/tests/integration/testsuite/Magento/Integration/Model/ConfigBasedIntegrationManagerTest.php new file mode 100644 index 00000000000..ee14b3ba53d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Integration/Model/ConfigBasedIntegrationManagerTest.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Integration\Model; + +/** + * Test class for \Magento\Integration\Model\ConfigBasedIntegrationManager.php. + */ +class ConfigBasedIntegrationManagerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $consolidatedMock; + + /** + * @var \Magento\Integration\Model\ConfigBasedIntegrationManager + */ + protected $integrationManager; + + /** + * @var \Magento\Integration\Api\IntegrationServiceInterface + */ + protected $integrationService; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->consolidatedMock = $this->createMock(\Magento\Integration\Model\ConsolidatedConfig::class); + $this->objectManager->addSharedInstance( + $this->consolidatedMock, + \Magento\Integration\Model\ConsolidatedConfig::class + ); + $this->integrationManager = $this->objectManager->create( + \Magento\Integration\Model\ConfigBasedIntegrationManager::class, + [] + ); + $this->integrationService = $this->objectManager->create( + \Magento\Integration\Api\IntegrationServiceInterface::class, + [] + ); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(\Magento\Integration\Model\ConsolidatedConfig::class); + parent::tearDown(); + } + + /** + * @magentoDbIsolation enabled + */ + public function testProcessConfigBasedIntegrations() + { + $newIntegrations = require __DIR__ . '/Config/Consolidated/_files/integration.php'; + $this->consolidatedMock + ->expects($this->any()) + ->method('getIntegrations') + ->willReturn($newIntegrations); + + // Check that the integrations do not exist already + foreach ($newIntegrations as $integrationName => $integrationData) { + $integration = $this->integrationService->findByName($integrationName); + $this->assertEquals(null, $integration->getId(), 'Integration already exists'); + } + + // Create new integrations + $this->assertEquals( + $newIntegrations, + $this->integrationManager->processConfigBasedIntegrations($newIntegrations), + 'Error processing config based integrations.' + ); + $createdIntegrations = []; + + // Check that the integrations are new with "inactive" status + foreach ($newIntegrations as $integrationName => $integrationData) { + $integration = $this->integrationService->findByName($integrationName); + $this->assertNotEmpty($integration->getId(), 'Integration was not created'); + $this->assertEquals( + $integration::STATUS_INACTIVE, + $integration->getStatus(), + 'Integration is not created with "inactive" status' + ); + $createdIntegrations[$integrationName] = $integration; + } + + // Rerun integration creation with the same data (data has not changed) + $this->assertEquals( + $newIntegrations, + $this->integrationManager->processConfigBasedIntegrations($newIntegrations), + 'Error processing config based integrations.' + ); + + // Check that the integrations are not recreated when data has not actually changed + foreach ($newIntegrations as $integrationName => $integrationData) { + $integration = $this->integrationService->findByName($integrationName); + $this->assertEquals( + $createdIntegrations[$integrationName]->getId(), + $integration->getId(), + 'Integration ID has changed' + ); + $this->assertEquals( + $createdIntegrations[$integrationName]->getStatus(), + $integration->getStatus(), + 'Integration status has changed' + ); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterQueueTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterQueueTest.php index 5c1a11756c1..7a0ac030d12 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterQueueTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterQueueTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Newsletter\Controller\Adminhtml; +use Magento\Framework\App\Request\Http as HttpRequest; + /** * @magentoAppArea adminhtml */ @@ -15,6 +17,9 @@ class NewsletterQueueTest extends \Magento\TestFramework\TestCase\AbstractBacken */ protected $_model; + /** + * @inheritDoc + */ protected function setUp() { parent::setUp(); @@ -23,6 +28,9 @@ protected function setUp() ); } + /** + * @inheritDoc + */ protected function tearDown() { /** @@ -47,6 +55,7 @@ public function testSaveActionQueueTemplateAndVerifySuccessMessage() 'subject' => 'test subject', 'text' => 'newsletter text', ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postForQueue); // Loading by code, since ID will vary. template_code is not actually used to load anywhere else. diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php index 50e89d92e43..ae57703f9e8 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php @@ -20,6 +20,9 @@ class NewsletterTemplateTest extends \Magento\TestFramework\TestCase\AbstractBac */ protected $model; + /** + * @inheritDoc + */ protected function setUp() { parent::setUp(); @@ -39,6 +42,9 @@ protected function setUp() ); } + /** + * @inheritDoc + */ protected function tearDown() { /** @@ -138,19 +144,6 @@ public function testSaveActionTemplateWithGetAndVerifyRedirect() $this->getRequest()->setMethod(\Zend\Http\Request::METHOD_GET)->setParam('id', $this->model->getId()); $this->dispatch('backend/newsletter/template/save'); - /** - * Check that errors was generated and set to session - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - - /** - * Check that correct redirect performed. - */ - $backendUrlModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Backend\Model\UrlInterface::class - ); - $backendUrlModel->turnOffSecretKey(); - $url = $backendUrlModel->getUrl('newsletter'); - $this->assertRedirect($this->stringStartsWith($url)); + $this->assertEquals(404, $this->getResponse()->getStatusCode()); } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php index b9573c99b44..e2638b5df1f 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php @@ -8,6 +8,7 @@ use Magento\Backend\Model\Session\Quote; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\Request\Http; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Message\MessageInterface; use Magento\Quote\Api\CartRepositoryInterface; @@ -38,6 +39,7 @@ public function testExecuteWithPaymentOperation() 'email' => $email ] ]; + $this->getRequest()->setMethod(Http::METHOD_POST); $this->getRequest()->setPostValue(['order' => $data]); /** @var OrderService|MockObject $orderService */ diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php index e071dde26a2..efb1b12fc60 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php @@ -8,10 +8,13 @@ use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Backend\Model\Session\Quote; use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @magentoAppArea adminhtml * @magentoDbIsolation enabled + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CreateTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -20,6 +23,11 @@ class CreateTest extends \Magento\TestFramework\TestCase\AbstractBackendControll */ protected $productRepository; + /** + * @inheritDoc + * + * @throws \Magento\Framework\Exception\AuthenticationException + */ protected function setUp() { parent::setUp(); @@ -27,8 +35,12 @@ protected function setUp() ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); } + /** + * Test LoadBlock being dispatched. + */ public function testLoadBlockAction() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('block', ','); $this->getRequest()->setParam('json', 1); $this->dispatch('backend/sales/order_create/loadBlock'); @@ -46,6 +58,7 @@ public function testLoadBlockActionData() )->addProducts( [$product->getId() => ['qty' => 1]] ); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('block', 'data'); $this->getRequest()->setParam('json', 1); $this->dispatch('backend/sales/order_create/loadBlock'); @@ -57,10 +70,14 @@ public function testLoadBlockActionData() } /** + * @param string $block Block name. + * @param string $expected Contains HTML. + * * @dataProvider loadBlockActionsDataProvider */ public function testLoadBlockActions($block, $expected) { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('block', $block); $this->getRequest()->setParam('json', 1); $this->dispatch('backend/sales/order_create/loadBlock'); @@ -68,6 +85,9 @@ public function testLoadBlockActions($block, $expected) $this->assertContains($expected, $html); } + /** + * @return array + */ public function loadBlockActionsDataProvider() { return [ @@ -90,6 +110,7 @@ public function testLoadBlockActionItems() )->addProducts( [$product->getId() => ['qty' => 1]] ); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setParam('block', 'items'); $this->getRequest()->setParam('json', 1); $this->dispatch('backend/sales/order_create/loadBlock'); @@ -215,6 +236,11 @@ public function testConfigureProductToAddAction() $this->assertContains(sprintf('"productId":"%s"', $product->getEntityId()), $body); } + /** + * Test not allowing to save. + * + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testDeniedSaveAction() { $this->_objectManager->configure( @@ -230,6 +256,7 @@ public function testDeniedSaveAction() \Magento\TestFramework\Helper\Bootstrap::getInstance() ->loadArea('adminhtml'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/sales/order_create/save'); $this->assertEquals('403', $this->getResponse()->getHttpResponseCode()); } @@ -263,6 +290,7 @@ public function testSyncBetweenQuoteAddresses() 'region' => 'Kyivska', 'region_id' => 1 ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'order' => ['billing_address' => $data], diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php index 43e98419798..7f14d8cf260 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php @@ -5,13 +5,14 @@ */ use Magento\Sales\Model\Order; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\Address as OrderAddress; require 'order.php'; /** @var Order $order */ -/** @var Order\Payment $payment */ -/** @var Order\Item $orderItem */ -/** @var Order\Address $billingAddress */ -/** @var Order\Address $shippingAddress */ +/** @var Order\Payment $payment */ +/** @var Order\Item $orderItem */ +/** @var array $addressData Data for creating addresses for the orders. */ $orders = [ [ 'increment_id' => '100000002', @@ -48,16 +49,30 @@ ], ]; +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); /** @var array $orderData */ foreach ($orders as $orderData) { /** @var $order \Magento\Sales\Model\Order */ $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Sales\Model\Order::class ); + + // Reset addresses + /** @var Order\Address $billingAddress */ + $billingAddress = $objectManager->create(OrderAddress::class, ['data' => $addressData]); + $billingAddress->setAddressType('billing'); + + $shippingAddress = clone $billingAddress; + $shippingAddress->setId(null)->setAddressType('shipping'); + $order ->setData($orderData) ->addItem($orderItem) + ->setCustomerIsGuest(true) + ->setCustomerEmail('customer@null.com') ->setBillingAddress($billingAddress) - ->setBillingAddress($shippingAddress) - ->save(); + ->setShippingAddress($shippingAddress); + + $orderRepository->save($order); } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php new file mode 100644 index 00000000000..33a8b4285d8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Magento\Framework\Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +/** @var Magento\SalesRule\Model\Rule $rule */ +$rule = $registry->registry('cart_rule_fixed_discount_coupon'); +if ($rule) { + $rule->delete(); +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php index 776c3022103..c9613c371bb 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php @@ -14,8 +14,19 @@ $objectManager = Bootstrap::getObjectManager(); +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = Bootstrap::getObjectManager()->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('name', '5$ fixed discount on whole cart') + ->create(); + +/** @var RuleRepositoryInterface $ruleRepository */ +$ruleRepository = Bootstrap::getObjectManager()->get(RuleRepositoryInterface::class); +$items = $ruleRepository->getList($searchCriteria) + ->getItems(); + +$salesRule = array_pop($items); + /** @var Rule $salesRule */ -$salesRule = getSalesRule('5$ fixed discount on whole cart'); if ($salesRule !== null) { /** @var RuleRepositoryInterface $ruleRepository */ $ruleRepository = $objectManager->get(RuleRepositoryInterface::class); @@ -29,18 +40,3 @@ $couponRepository = $objectManager->get(CouponRepositoryInterface::class); $couponRepository->deleteById($coupon->getCouponId()); } - -function getSalesRule(string $name) -{ - /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ - $searchCriteriaBuilder = Bootstrap::getObjectManager()->get(SearchCriteriaBuilder::class); - $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) - ->create(); - - /** @var RuleRepositoryInterface $ruleRepository */ - $ruleRepository = Bootstrap::getObjectManager()->get(RuleRepositoryInterface::class); - $items = $ruleRepository->getList($searchCriteria) - ->getItems(); - - return array_pop($items); -} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php index 969d9530ae5..e222e6fda48 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Product/AttributeTest.php @@ -7,6 +7,8 @@ namespace Magento\Swatches\Controller\Adminhtml\Product; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Exception\LocalizedException; /** @@ -17,6 +19,21 @@ */ class AttributeTest extends \Magento\TestFramework\TestCase\AbstractBackendController { + /** + * @var FormKey + */ + private $formKey; + + /** + * @inheritDoc + */ + protected function setUp() + { + parent::setUp(); + + $this->formKey = $this->_objectManager->get(FormKey::class); + } + /** * Generate random hex color. * @@ -112,7 +129,6 @@ private function getAttributePreset() : array { return [ 'serialized_options' => '[]', - 'form_key' => 'XxtpPYjm2YPYUlAt', 'frontend_label' => [ 0 => 'asdasd', 1 => '', @@ -175,7 +191,9 @@ public function testLargeOptionsDataSet( int $expectedOptionsCount, array $expectedLabels ) : void { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($attributeData); + $this->getRequest()->setPostValue('form_key', $this->formKey->getFormKey()); $this->dispatch('backend/catalog/product_attribute/save'); $entityTypeId = $this->_objectManager->create( \Magento\Eav\Model\Entity::class diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Observer/AddSwatchAttributeTypeObserverTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Observer/AddSwatchAttributeTypeObserverTest.php new file mode 100644 index 00000000000..15d10c2ddff --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Observer/AddSwatchAttributeTypeObserverTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Observer; + +use Magento\Framework\DataObject; +use Magento\Swatches\Model\Swatch; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Event\ManagerInterface; + +/** + * Test checks that swatch types are added to the other attribute types + */ +class AddSwatchAttributeTypeObserverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoAppArea adminhtml + */ + public function testAddSwatchAttributeTypes() + { + $objectManager = Bootstrap::getObjectManager(); + $eventManager = $objectManager->get(ManagerInterface::class); + $response = new DataObject(); + $response->setTypes([]); + + $eventManager->dispatch( + 'adminhtml_product_attribute_types', + ['response' => $response] + ); + + $responseTypes = $response->getTypes(); + + self::assertGreaterThan(0, count($responseTypes)); + + /* Iterate through values since other types (not swatches) might be added by observers */ + $responseTypeValues = []; + foreach ($responseTypes as $responseType) { + $responseTypeValues[] = $responseType['value']; + } + + self::assertTrue(in_array(Swatch::SWATCH_TYPE_VISUAL_ATTRIBUTE_FRONTEND_INPUT, $responseTypeValues)); + self::assertTrue(in_array(Swatch::SWATCH_TYPE_TEXTUAL_ATTRIBUTE_FRONTEND_INPUT, $responseTypeValues)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php b/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php index 95fcf24894c..cb684c02168 100644 --- a/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php @@ -6,6 +6,7 @@ namespace Magento\Theme\Controller\Adminhtml\System\Design\Config; +use Magento\Framework\App\Request\Http; use Magento\Framework\Data\Form\FormKey; use Magento\TestFramework\TestCase\AbstractBackendController; @@ -29,6 +30,9 @@ class SaveTest extends AbstractBackendController */ protected $uri = 'backend/theme/design_config/save'; + /** + * @inheritdoc + */ protected function setUp() { parent::setUp(); @@ -36,6 +40,7 @@ protected function setUp() $this->formKey = $this->_objectManager->get( FormKey::class ); + $this->httpMethod = Http::METHOD_POST; } /** @@ -107,12 +112,11 @@ private function getRequestParams() ]; } + /** + * @inheritDoc + */ public function testAclHasAccess() { - $this->getRequest()->setMethod( - \Zend\Http\Request::METHOD_POST - ); - $this->getRequest()->setParams( [ 'form_key' => $this->formKey->getFormKey() diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php index 1a726c4e2c1..5f4964d042d 100644 --- a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php @@ -5,6 +5,9 @@ */ namespace Magento\User\Controller\Adminhtml; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\Helper\Bootstrap; + /** * Test class for \Magento\User\Controller\Adminhtml\Auth * @@ -35,7 +38,7 @@ public function testForgotpasswordAction() $this->dispatch('backend/admin/auth/forgotpassword'); $this->assertRedirect( $this->equalTo( - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Backend\Helper\Data::class )->getHomePageUrl() ) @@ -51,22 +54,25 @@ public function testForgotpasswordAction() */ public function testEmailSendForgotPasswordAction() { - $transportBuilderMock = $this->prepareEmailMock( - 1, - 'admin_emails_forgot_email_template', - 'general' + /** @var TransportBuilderMock $transportMock */ + $transportMock = Bootstrap::getObjectManager()->get( + TransportBuilderMock::class ); - $this->addMockToClass($transportBuilderMock, \Magento\User\Model\User::class); - $this->getRequest()->setPostValue('email', 'adminUser@example.com'); $this->dispatch('backend/admin/auth/forgotpassword'); $this->assertRedirect( $this->equalTo( - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Backend\Helper\Data::class )->getHomePageUrl() ) ); + $message = $transportMock->getSentMessage(); + $this->assertNotEmpty($message); + $this->assertEquals( + __('Password Reset Confirmation for %1', ['John Doe'])->render(), + $message->getSubject() + ); } /** @@ -79,13 +85,13 @@ public function testEmailSendForgotPasswordAction() public function testResetPasswordAction() { /** @var $user \Magento\User\Model\User */ - $user = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $user = Bootstrap::getObjectManager()->create( \Magento\User\Model\User::class )->loadByUsername( 'dummy_username' ); $this->assertNotEmpty($user->getId(), 'Broken fixture'); - $resetPasswordToken = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + $resetPasswordToken = Bootstrap::getObjectManager()->get( \Magento\User\Helper\Data::class )->generateResetPasswordLinkToken(); $user->changeResetPasswordLinkToken($resetPasswordToken); @@ -123,7 +129,7 @@ public function testResetPasswordActionWithDummyToken() */ public function testResetPasswordPostAction($password, $passwordConfirmation, $isPasswordChanged) { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); /** @var $user \Magento\User\Model\User */ $user = $objectManager->create(\Magento\User\Model\User::class); @@ -203,7 +209,7 @@ public function testResetPasswordPostActionWithDummyToken() \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); /** @var \Magento\Backend\Helper\Data $backendHelper */ $backendHelper = $objectManager->get(\Magento\Backend\Helper\Data::class); @@ -218,7 +224,7 @@ public function testResetPasswordPostActionWithDummyToken() */ public function testResetPasswordPostActionWithInvalidPassword() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); $user = $objectManager->create(\Magento\User\Model\User::class); $user->loadByUsername('dummy_username'); diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/UserTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/UserTest.php index 995ec5f6bed..9cf8fc43951 100644 --- a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/UserTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/UserTest.php @@ -6,6 +6,7 @@ namespace Magento\User\Controller\Adminhtml; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\TestFramework\Bootstrap; /** @@ -35,6 +36,7 @@ public function testIndexAction() */ public function testSaveActionNoData() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/admin/user/save'); $this->assertRedirect($this->stringContains('backend/admin/user/index/')); } @@ -55,6 +57,7 @@ public function testSaveActionWrongId() $userId = $user->getId(); $this->assertNotEmpty($userId, 'Broken fixture'); $user->delete(); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue('user_id', $userId); $this->dispatch('backend/admin/user/save'); $this->assertSessionMessages( @@ -72,6 +75,7 @@ public function testSaveActionWrongId() public function testSaveActionMissingCurrentAdminPassword() { $fixture = uniqid(); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'username' => $fixture, @@ -99,6 +103,7 @@ public function testSaveActionMissingCurrentAdminPassword() public function testSaveAction() { $fixture = uniqid(); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'username' => $fixture, @@ -126,6 +131,7 @@ public function testSaveAction() */ public function testSaveActionDuplicateUser() { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'username' => 'adminUser', @@ -147,13 +153,17 @@ public function testSaveActionDuplicateUser() } /** - * Verify password change properly updates fields when the request is valid + * Verify password change properly updates fields when the request is valid. + * + * @param array $postData + * @param bool $isPasswordCorrect * * @magentoDbIsolation enabled * @dataProvider saveActionPasswordChangeDataProvider */ public function testSaveActionPasswordChange($postData, $isPasswordCorrect) { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($postData); $this->dispatch('backend/admin/user/save'); diff --git a/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/WidgetTest.php b/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/WidgetTest.php index 26ed706e3ec..f5b5fe533e1 100644 --- a/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/WidgetTest.php +++ b/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/WidgetTest.php @@ -15,7 +15,7 @@ class WidgetTest extends \Magento\TestFramework\TestCase\AbstractBackendControll */ public function testLoadOptionsAction() { - $this->getRequest()->setPostValue( + $this->getRequest()->setParam( 'widget', '{"widget_type":"Magento\\\\Cms\\\\Block\\\\Widget\\\\Page\\\\Link","values":{}}' ); diff --git a/dev/tests/setup-integration/etc/install-config-mysql.php.dist b/dev/tests/setup-integration/etc/install-config-mysql.php.dist index c5757b00110..d355100c446 100644 --- a/dev/tests/setup-integration/etc/install-config-mysql.php.dist +++ b/dev/tests/setup-integration/etc/install-config-mysql.php.dist @@ -17,8 +17,8 @@ return [ 'admin-email' => \Magento\TestFramework\Bootstrap::ADMIN_EMAIL, 'admin-firstname' => \Magento\TestFramework\Bootstrap::ADMIN_FIRSTNAME, 'admin-lastname' => \Magento\TestFramework\Bootstrap::ADMIN_LASTNAME, - 'enable_modules' => 'Magento_TestSetupModule2,Magento_TestSetupModule1,Magento_Backend', - 'disable_modules' => 'all' + 'enable-modules' => 'Magento_TestSetupModule2,Magento_TestSetupModule1,Magento_Backend', + 'disable-modules' => 'all' ], 'checkout' => [ 'host' => '{{db-host}}', diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php new file mode 100644 index 00000000000..fb5938be613 --- /dev/null +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CodeMessDetector\Rule\Design; + +use PDepend\Source\AST\ASTClass; +use PHPMD\AbstractNode; +use PHPMD\AbstractRule; +use PHPMD\Node\ClassNode; +use PHPMD\Rule\ClassAware; +use Magento\Framework\App\ActionInterface; + +/** + * Actions must process a defined list of HTTP methods. + */ +class AllPurposeAction extends AbstractRule implements ClassAware +{ + /** + * @inheritdoc + * + * @param ClassNode|ASTClass $node + */ + public function apply(AbstractNode $node) + { + try { + $impl = class_implements($node->getFullQualifiedName(), true); + } catch (\Throwable $exception) { + //Couldn't load a class. + return; + } + + if (in_array(ActionInterface::class, $impl, true)) { + $methodsDefined = false; + foreach ($impl as $i) { + if (preg_match('/\\\Http[a-z]+ActionInterface$/i', $i)) { + $methodsDefined = true; + break; + } + } + if (!$methodsDefined) { + $this->addViolation($node, [$node->getFullQualifiedName()]); + } + } + } +} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Test/Unit/Rule/Design/AllPurposeActionTest.php b/dev/tests/static/framework/Magento/CodeMessDetector/Test/Unit/Rule/Design/AllPurposeActionTest.php new file mode 100644 index 00000000000..408853141e5 --- /dev/null +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Test/Unit/Rule/Design/AllPurposeActionTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CodeMessDetector\Test\Unit\Rule\Design; + +use Magento\CodeMessDetector\Rule\Design\AllPurposeAction; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\ActionInterface; +use PHPUnit\Framework\TestCase as TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMocker; +use PHPMD\Report; +use PHPMD\Node\ClassNode; + +class AllPurposeActionTest extends TestCase +{ + /** + * @param object $fakeAction + * @param bool $violates + * + * @dataProvider getCases + */ + public function testApply($fakeAction, bool $violates) + { + $node = $this->createNodeMock($fakeAction); + $rule = new AllPurposeAction(); + $this->expectsRuleViolation($rule, $violates); + $rule->apply($node); + } + + /** + * @return array + */ + public function getCases(): array + { + return [ + [ + new class implements ActionInterface, HttpGetActionInterface { + /** + * @inheritDoc + */ + public function execute() + { + return null; + } + }, + false + ], + [ + new class implements ActionInterface { + /** + * @inheritDoc + */ + public function execute() + { + return null; + } + }, + true + ], + [ + new class implements HttpGetActionInterface { + /** + * @inheritDoc + */ + public function execute() + { + return null; + } + }, + false + ], + [ + new class { + + }, + false + ] + ]; + } + + /** + * @param object $dynamicObject + * + * @return ClassNode|MockObject + */ + private function createNodeMock($dynamicObject): MockObject + { + $node = $this->getMockBuilder(ClassNode::class) + ->disableOriginalConstructor() + ->disableProxyingToOriginalMethods() + ->setMethods([ + // disable name lookup from AST artifact + 'getNamespaceName', + 'getParentName', + 'getName', + 'getFullQualifiedName', + ]) + ->getMock(); + $node->expects($this->any()) + ->method('getFullQualifiedName') + ->willReturn(get_class($dynamicObject)); + + return $node; + } + + /** + * @param AllPurposeAction $rule + * @param bool $expects + * @return InvocationMocker + */ + private function expectsRuleViolation( + AllPurposeAction $rule, + bool $expects + ): InvocationMocker { + /** @var Report|MockObject $report */ + $report = $this->getMockBuilder(Report::class)->getMock(); + if ($expects) { + $violationExpectation = $this->atLeastOnce(); + } else { + $violationExpectation = $this->never(); + } + $invokation = $report->expects($violationExpectation) + ->method('addRuleViolation'); + $rule->setReport($report); + + return $invokation; + } +} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml index 61d4f7b3be8..c9bfe4fe6e3 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml +++ b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml @@ -29,6 +29,31 @@ final class Foo } class Baz { final public function bad() {} +} + ]]> + </example> + </rule> + <rule name="AllPurposeAction" + class="Magento\CodeMessDetector\Rule\Design\AllPurposeAction" + message= "The class {0} does not restrict processed HTTP methods by implementing a Http*Method name*ActionInterface"> + <description> + <![CDATA[ +Controllers (classes implementing ActionInterface) have to implement marker Http<Method>ActionInterface +to restrict incoming requests by methods. + ]]> + </description> + <priority>2</priority> + <properties /> + <example> + <![CDATA[ +class PostOrder implements ActionInterface +{ + public function execute() + { + //I process GET, POST, PATCH etc. while only intended for POST + ... + return $response; + } } ]]> </example> diff --git a/dev/tests/static/framework/Magento/ruleset.xml b/dev/tests/static/framework/Magento/ruleset.xml index cc0a5f43e08..d0f0d38f8fa 100644 --- a/dev/tests/static/framework/Magento/ruleset.xml +++ b/dev/tests/static/framework/Magento/ruleset.xml @@ -10,6 +10,10 @@ <rule ref="PSR2"/> + <rule ref="PSR2.Files.ClosingTag"> + <exclude-pattern>*.phtml</exclude-pattern> + </rule> + <rule ref="Magento.Files.LineLength"> <properties> <property name="lineLimit" value="120"/> @@ -26,18 +30,21 @@ <exclude-pattern>*/_files/*</exclude-pattern> <exclude-pattern>*/Test/*</exclude-pattern> <exclude-pattern>*Test.php</exclude-pattern> + <exclude-pattern>*/Magento/Inventory*/*</exclude-pattern> </rule> <rule ref="Magento.Annotation.MethodAnnotationStructure"> <include-pattern>*\.(php)</include-pattern> <exclude-pattern>*/Test/*</exclude-pattern> <exclude-pattern>*Test.php</exclude-pattern> <exclude-pattern>*/_files/*</exclude-pattern> + <exclude-pattern>*/Magento/Inventory*/*</exclude-pattern> </rule> <rule ref="Magento.Annotation.ClassAnnotationStructure"> <include-pattern>*\.(php)</include-pattern> <exclude-pattern>*/Test/*</exclude-pattern> <exclude-pattern>*Test.php</exclude-pattern> <exclude-pattern>*/_files/*</exclude-pattern> + <exclude-pattern>*/Magento/Inventory*/*</exclude-pattern> </rule> <rule ref="Magento.Functions.OutputBuffering"> <include-pattern>*/(app/code|vendor|setup/src|lib/internal/Magento)/*</include-pattern> diff --git a/dev/tests/static/get_github_changes.php b/dev/tests/static/get_github_changes.php index dba235c184e..a619f951dac 100644 --- a/dev/tests/static/get_github_changes.php +++ b/dev/tests/static/get_github_changes.php @@ -147,6 +147,8 @@ function getRepo($options, $mainline) } /** + * Combine list of changed files based on comparison between forks. + * * @param string $mainline * @param GitRepo $repo * @param string $branchName @@ -158,6 +160,8 @@ function retrieveChangesAcrossForks($mainline, GitRepo $repo, $branchName) } /** + * Combine list of new files based on comparison between forks. + * * @param string $mainline * @param GitRepo $repo * @param string $branchName @@ -172,7 +176,7 @@ function retrieveNewFilesAcrossForks($mainline, GitRepo $repo, $branchName) * Deletes temporary "base" repo * * @param GitRepo $repo - * @param string $repo + * @param string $mainline */ function cleanup($repo, $mainline) { @@ -392,6 +396,7 @@ private function buildChangedFilesMask(int $changesType): array /** * Find one of the allowed modification mask returned by git diff. + * * Example of change record: "A path/to/added_file" * * @param string $changeRecord @@ -427,7 +432,7 @@ private function getFileChangeDetails(string $fileName, string $remoteAlias, str 'diff HEAD %s/%s -- %s', $remoteAlias, $remoteBranch, - $this->workTree . '/' . $fileName + $fileName ) ); diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php index 81088522bcc..a4baacbe4d1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php @@ -96,35 +96,6 @@ function ($file) { ); } - public function testAppCodeUsage() - { - $files = Files::init(); - $componentRegistrar = new ComponentRegistrar(); - $libPaths = $componentRegistrar->getPaths(ComponentRegistrar::LIBRARY); - $invoker = new AggregateInvoker($this); - $invoker( - function ($file) use ($libPaths) { - $content = file_get_contents($file); - foreach ($libPaths as $libPath) { - if (strpos($file, $libPath) === 0) { - $this->assertSame( - 0, - preg_match('~(?<![a-z\\d_:]|->|function\\s)__\\s*\\(~iS', $content), - 'Function __() is defined outside of the library and must not be used there. ' . - 'Replacement suggestion: new \\Magento\\Framework\\Phrase()' - ); - } - } - }, - $files->getPhpFiles( - Files::INCLUDE_PUB_CODE | - Files::INCLUDE_LIBS | - Files::AS_DATA_SET | - Files::INCLUDE_NON_CLASSES - ) - ); - } - /** * @inheritdoc */ diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index fd8c09907e0..8fa70be004f 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -202,3 +202,6 @@ setup/performance-toolkit/aggregate-report Magento/MessageQueue/Setup Magento/Elasticsearch/Elasticsearch5 Test/_files +Magento/InventoryCatalogAdminUi/Controller/Adminhtml +Magento/InventoryConfigurableProductIndexer/Indexer +Magento/InventoryGroupedProductIndexer/Indexer diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml index 76f76e2b23c..fddb1e6fdfc 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml @@ -47,5 +47,6 @@ <!-- Magento Specific Rules --> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/FinalImplementation" /> + <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/AllPurposeAction" /> </ruleset> diff --git a/lib/internal/Magento/Framework/Amqp/Config.php b/lib/internal/Magento/Framework/Amqp/Config.php index 8fb827d9eb0..684c5cd38b1 100644 --- a/lib/internal/Magento/Framework/Amqp/Config.php +++ b/lib/internal/Magento/Framework/Amqp/Config.php @@ -131,10 +131,12 @@ public function __destruct() public function getValue($key) { $this->load(); - return isset($this->data[$key]) ? $this->data[$key] : null; + return $this->data[$key] ?? null; } /** + * Create amqp connection + * * @return AbstractConnection */ private function createConnection(): AbstractConnection diff --git a/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php b/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php index a1f5acb4e60..49d824a4f2e 100644 --- a/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php +++ b/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php @@ -8,6 +8,9 @@ use Magento\Framework\Convert\ConvertArray; use Magento\Framework\Reflection\DataObjectProcessor; +/** + * Data object converter. + */ class SimpleDataObjectConverter { /** @@ -156,14 +159,13 @@ public static function snakeCaseToUpperCamelCase($input) */ public static function snakeCaseToCamelCase($input) { - return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $input)))); + return lcfirst(self::snakeCaseToUpperCamelCase($input)); } /** * Convert a CamelCase string read from method into field key in snake_case * - * e.g. DefaultShipping => default_shipping - * Postcode => postcode + * For example [DefaultShipping => default_shipping, Postcode => postcode] * * @param string $name * @return string diff --git a/lib/internal/Magento/Framework/App/Action/Forward.php b/lib/internal/Magento/Framework/App/Action/Forward.php index 7d6f956545b..c81bc48ace4 100644 --- a/lib/internal/Magento/Framework/App/Action/Forward.php +++ b/lib/internal/Magento/Framework/App/Action/Forward.php @@ -10,6 +10,11 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; +/** + * Forward request further. + * + * @SuppressWarnings(PHPMD.AllPurposeAction) + */ class Forward extends AbstractAction { /** diff --git a/lib/internal/Magento/Framework/App/Action/HttpConnectActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpConnectActionInterface.php new file mode 100644 index 00000000000..426fe584bad --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpConnectActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing CONNECT requests. + */ +interface HttpConnectActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpDeleteActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpDeleteActionInterface.php new file mode 100644 index 00000000000..174f21cc57b --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpDeleteActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing DELETE requests. + */ +interface HttpDeleteActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpGetActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpGetActionInterface.php new file mode 100644 index 00000000000..308b77aa8db --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpGetActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing GET requests. + */ +interface HttpGetActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php new file mode 100644 index 00000000000..d2f9b70913c --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpHeadActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing HEAD requests. + */ +interface HttpHeadActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpOptionsActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpOptionsActionInterface.php new file mode 100644 index 00000000000..fa768885a3d --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpOptionsActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing OPTIONS requests. + */ +interface HttpOptionsActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpPatchActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpPatchActionInterface.php new file mode 100644 index 00000000000..bfc1ec94adc --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpPatchActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing PATCH requests. + */ +interface HttpPatchActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpPostActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpPostActionInterface.php new file mode 100644 index 00000000000..a4b87ecfe84 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpPostActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing POST requests. + */ +interface HttpPostActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpPropfindActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpPropfindActionInterface.php new file mode 100644 index 00000000000..7ddd32c4727 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpPropfindActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing PROPFIND requests. + */ +interface HttpPropfindActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpPutActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpPutActionInterface.php new file mode 100644 index 00000000000..a83e946d9a9 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpPutActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing PUT requests. + */ +interface HttpPutActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/HttpTraceActionInterface.php b/lib/internal/Magento/Framework/App/Action/HttpTraceActionInterface.php new file mode 100644 index 00000000000..b776ab061e6 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Action/HttpTraceActionInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Action; + +use Magento\Framework\App\ActionInterface; + +/** + * Marker for actions processing TRACE requests. + */ +interface HttpTraceActionInterface extends ActionInterface +{ + +} diff --git a/lib/internal/Magento/Framework/App/Action/Redirect.php b/lib/internal/Magento/Framework/App/Action/Redirect.php index 702d78c84f6..9e211edfc00 100644 --- a/lib/internal/Magento/Framework/App/Action/Redirect.php +++ b/lib/internal/Magento/Framework/App/Action/Redirect.php @@ -10,6 +10,11 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; +/** + * Issue a redirect. + * + * @SuppressWarnings(PHPMD.AllPurposeAction) + */ class Redirect extends AbstractAction { /** diff --git a/lib/internal/Magento/Framework/App/Cache/State.php b/lib/internal/Magento/Framework/App/Cache/State.php index 08c1fca66ea..9d268ac2d1b 100644 --- a/lib/internal/Magento/Framework/App/Cache/State.php +++ b/lib/internal/Magento/Framework/App/Cache/State.php @@ -11,6 +11,9 @@ use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\Config\File\ConfigFilePool; +/** + * Cache State + */ class State implements StateInterface { /** @@ -74,7 +77,7 @@ public function __construct(DeploymentConfig $config, Writer $writer, $banAll = public function isEnabled($cacheType) { $this->load(); - return isset($this->statuses[$cacheType]) ? (bool)$this->statuses[$cacheType] : false; + return (bool)($this->statuses[$cacheType] ?? false); } /** diff --git a/lib/internal/Magento/Framework/App/FrontController.php b/lib/internal/Magento/Framework/App/FrontController.php index b00cb3ed609..ba23e010e3e 100644 --- a/lib/internal/Magento/Framework/App/FrontController.php +++ b/lib/internal/Magento/Framework/App/FrontController.php @@ -14,6 +14,7 @@ use Magento\Framework\Message\ManagerInterface as MessageManager; use Magento\Framework\App\Action\AbstractAction; use Psr\Log\LoggerInterface; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -45,6 +46,11 @@ class FrontController implements FrontControllerInterface */ private $logger; + /** + * @var bool + */ + private $validatedRequest = false; + /** * @param RouterListInterface $routerList * @param ResponseInterface $response @@ -72,13 +78,14 @@ public function __construct( /** * Perform action and generate response * - * @param RequestInterface $request + * @param RequestInterface|HttpRequest $request * @return ResponseInterface|ResultInterface * @throws \LogicException */ public function dispatch(RequestInterface $request) { \Magento\Framework\Profiler::start('routers_match'); + $this->validatedRequest = false; $routingCycleCounter = 0; $result = null; while (!$request->isDispatched() && $routingCycleCounter++ < 100) { @@ -109,44 +116,56 @@ public function dispatch(RequestInterface $request) } /** - * @param RequestInterface $request + * @param HttpRequest $request * @param ActionInterface $actionInstance * @throws NotFoundException * * @return ResponseInterface|ResultInterface */ private function processRequest( - RequestInterface $request, + HttpRequest $request, ActionInterface $actionInstance ) { $request->setDispatched(true); $this->response->setNoCacheHeaders(); - //Validating request. - try { - $this->requestValidator->validate( - $request, - $actionInstance - ); + $result = null; + + //Validating a request only once. + if (!$this->validatedRequest) { + try { + $this->requestValidator->validate( + $request, + $actionInstance + ); + } catch (InvalidRequestException $exception) { + //Validation failed - processing validation results. + $this->logger->debug( + 'Request validation failed for action "' + .get_class($actionInstance) .'"' + ); + $result = $exception->getReplaceResult(); + if ($messages = $exception->getMessages()) { + foreach ($messages as $message) { + $this->messages->addErrorMessage($message); + } + } + } + $this->validatedRequest = true; + } + //Validation did not produce a result to replace the action's. + if (!$result) { if ($actionInstance instanceof AbstractAction) { $result = $actionInstance->dispatch($request); } else { $result = $actionInstance->execute(); } - } catch (InvalidRequestException $exception) { - //Validation failed - processing validation results. - $this->logger->debug( - 'Request validation failed for action "' - .get_class($actionInstance) .'"' - ); - $result = $exception->getReplaceResult(); - if ($messages = $exception->getMessages()) { - foreach ($messages as $message) { - $this->messages->addErrorMessage($message); - } - } } + //handling redirect to 404 + if ($result instanceof NotFoundException) { + throw $result; + } return $result; } } diff --git a/lib/internal/Magento/Framework/App/Request/CompositeValidator.php b/lib/internal/Magento/Framework/App/Request/CompositeValidator.php new file mode 100644 index 00000000000..2b5205fddc9 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Request/CompositeValidator.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\RequestInterface; + +/** + * Use sequence of validators to validate requests. + */ +class CompositeValidator implements ValidatorInterface +{ + /** + * @var ValidatorInterface[] + */ + private $validators; + + /** + * @param ValidatorInterface[] $validators + */ + public function __construct(array $validators) + { + $this->validators = $validators; + } + + /** + * @inheritDoc + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void { + foreach ($this->validators as $validator) { + $validator->validate($request, $action); + } + } +} diff --git a/lib/internal/Magento/Framework/App/Request/DataPersistor.php b/lib/internal/Magento/Framework/App/Request/DataPersistor.php index bb3740aceb0..f0b8819621a 100644 --- a/lib/internal/Magento/Framework/App/Request/DataPersistor.php +++ b/lib/internal/Magento/Framework/App/Request/DataPersistor.php @@ -5,8 +5,12 @@ */ namespace Magento\Framework\App\Request; +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\Session\SessionManagerInterface; +/** + * Persist data to session. + */ class DataPersistor implements DataPersistorInterface { /** @@ -32,7 +36,7 @@ public function __construct( */ public function set($key, $data) { - $method = 'set' . ucfirst($key) . 'Data'; + $method = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key) . 'Data'; call_user_func_array([$this->session, $method], [$data]); } @@ -44,7 +48,7 @@ public function set($key, $data) */ public function get($key) { - $method = 'get' . ucfirst($key) . 'Data'; + $method = 'get' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key) . 'Data'; return call_user_func_array([$this->session, $method], []); } @@ -56,7 +60,7 @@ public function get($key) */ public function clear($key) { - $method = 'uns' . ucfirst($key) . 'Data'; + $method = 'uns' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key) . 'Data'; call_user_func_array([$this->session, $method], []); } } diff --git a/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php b/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php new file mode 100644 index 00000000000..4e7216954b0 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +/** + * Map of HTTP methods and interfaces that an action implements in order to process them. + */ +class HttpMethodMap +{ + /** + * @var string[] + */ + private $map; + + /** + * @param string[] $map + */ + public function __construct(array $map) + { + $this->map = $this->processMap($map); + } + + /** + * Filter given map. + * + * @param array $map + * @throws \InvalidArgumentException + * + * @return string[] + */ + private function processMap(array $map): array + { + $filtered = []; + foreach ($map as $method => $interface) { + $interface = trim(preg_replace('/^\\\+/', '', $interface)); + if (!(interface_exists($interface) || class_exists($interface))) { + throw new \InvalidArgumentException( + "Interface '$interface' does not exist" + ); + } + if (!$method) { + throw new \InvalidArgumentException('Invalid method given'); + } + + $filtered[$method] = $interface; + } + + return $filtered; + } + + /** + * Where keys are methods' names and values are interfaces' names. + * + * @return string[] + * + * @see \Zend\Http\Request Has list of methods as METHOD_* constants. + */ + public function getMap(): array + { + return $this->map; + } +} diff --git a/lib/internal/Magento/Framework/App/Request/HttpMethodValidator.php b/lib/internal/Magento/Framework/App/Request/HttpMethodValidator.php new file mode 100644 index 00000000000..d3eb514caad --- /dev/null +++ b/lib/internal/Magento/Framework/App/Request/HttpMethodValidator.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Interception\InterceptorInterface; +use Magento\Framework\Phrase; +use Psr\Log\LoggerInterface; + +/** + * Make sure that a request's method can be processed by an action. + */ +class HttpMethodValidator implements ValidatorInterface +{ + /** + * @var HttpMethodMap + */ + private $map; + + /** + * @var LoggerInterface + */ + private $log; + + /** + * @param HttpMethodMap $map + * @param LoggerInterface $logger + */ + public function __construct( + HttpMethodMap $map, + LoggerInterface $logger + ) { + $this->map = $map; + $this->log = $logger; + } + + /** + * Create exception when invalid HTTP method used. + * + * @param Http $request + * @param ActionInterface $action + * @throws InvalidRequestException + * + * @return void + */ + private function throwException( + Http $request, + ActionInterface $action + ): void { + $uri = $request->getRequestUri(); + $method = $request->getMethod(); + if ($action instanceof InterceptorInterface) { + $actionClass = get_parent_class($action); + } else { + $actionClass = get_class($action); + } + $this->log->debug( + "URI '$uri'' cannot be accessed with $method method ($actionClass)" + ); + + throw new InvalidRequestException( + new NotFoundException(new Phrase('Page not found.')) + ); + } + + /** + * @inheritDoc + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void { + if ($request instanceof Http) { + $method = $request->getMethod(); + $map = $this->map->getMap(); + //If we don't have an interface for the HTTP method or + //the action has HTTP method limitations and doesn't allow the + //received one then the request is invalid. + if (!array_key_exists($method, $map) + || (array_intersect($map, class_implements($action, true)) + && !$action instanceof $map[$method] + ) + ) { + $this->throwException($request, $action); + } + } + } +} diff --git a/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php b/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php index 3d408b00506..f15ce494e9b 100644 --- a/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php +++ b/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php @@ -10,6 +10,7 @@ use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Exception\RuntimeException; use Magento\Framework\Phrase; @@ -29,8 +30,9 @@ class InvalidRequestException extends RuntimeException private $messages; /** - * @param ResponseInterface|ResultInterface $replaceResult Use this result - * instead of calling action instance. + * @param ResponseInterface|ResultInterface|NotFoundException $replaceResult + * Use this result instead of calling an action instance, + * if NotFoundException is given the the default 404 mechanism will be triggered. * @param Phrase[]|null $messages Messages to show to client * as error messages. */ @@ -43,7 +45,7 @@ public function __construct($replaceResult, ?array $messages = null) } /** - * @return ResponseInterface|ResultInterface + * @return ResponseInterface|ResultInterface|NotFoundException */ public function getReplaceResult() { diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpMethodMapTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpMethodMapTest.php new file mode 100644 index 00000000000..6215da3ae90 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpMethodMapTest.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Test\Unit\Request; + +use Magento\Framework\App\Request\HttpMethodMap; +use PHPUnit\Framework\TestCase; + +class HttpMethodMapTest extends TestCase +{ + /** + * Test filtering of interface names. + */ + public function testFilter() + { + $map = new HttpMethodMap( + ['method1' => '\\Throwable', 'method2' => 'DateTime'] + ); + $this->assertEquals( + ['method1' => \Throwable::class, 'method2' => \DateTime::class], + $map->getMap() + ); + } + + /** + * Test validation of interface names. + * + * @expectedException \InvalidArgumentException + */ + public function testExisting() + { + new HttpMethodMap(['method1' => 'NonExistingClass']); + } + + /** + * Test validation of method names. + * + * @expectedException \InvalidArgumentException + */ + public function testMethod() + { + new HttpMethodMap([\Throwable::class]); + } +} diff --git a/lib/internal/Magento/Framework/Archive/Tar.php b/lib/internal/Magento/Framework/Archive/Tar.php index 7fe1255e5b8..a858b241151 100644 --- a/lib/internal/Magento/Framework/Archive/Tar.php +++ b/lib/internal/Magento/Framework/Archive/Tar.php @@ -4,15 +4,15 @@ * See COPYING.txt for license details. */ +namespace Magento\Framework\Archive; + +use Magento\Framework\Archive\Helper\File; + /** * Class to work with tar archives * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Framework\Archive; - -use Magento\Framework\Archive\Helper\File; - class Tar extends \Magento\Framework\Archive\AbstractArchive implements \Magento\Framework\Archive\ArchiveInterface { /** @@ -259,10 +259,7 @@ protected function _createTar($skipRoot = false, $finalize = false) ); } - array_shift($dirFiles); - /* remove './'*/ - array_shift($dirFiles); - /* remove '../'*/ + $dirFiles = array_diff($dirFiles, ['..', '.']); foreach ($dirFiles as $item) { $this->_setCurrentFile($file . $item)->_createTar(); diff --git a/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php b/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php index f3af6914269..2ca64bf14d3 100644 --- a/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php +++ b/lib/internal/Magento/Framework/Communication/Config/CompositeReader.php @@ -27,12 +27,10 @@ public function __construct(array $readers) usort( $readers, function ($firstItem, $secondItem) { - if (!isset($firstItem['sortOrder']) || !isset($secondItem['sortOrder']) - || $firstItem['sortOrder'] == $secondItem['sortOrder'] - ) { + if (!isset($firstItem['sortOrder']) || !isset($secondItem['sortOrder'])) { return 0; } - return $firstItem['sortOrder'] < $secondItem['sortOrder'] ? -1 : 1; + return $firstItem['sortOrder'] <=> $secondItem['sortOrder']; } ); $this->readers = []; diff --git a/lib/internal/Magento/Framework/Communication/Config/Validator.php b/lib/internal/Magento/Framework/Communication/Config/Validator.php index 5e2687c3b08..76ef1b85b63 100644 --- a/lib/internal/Magento/Framework/Communication/Config/Validator.php +++ b/lib/internal/Magento/Framework/Communication/Config/Validator.php @@ -38,6 +38,8 @@ public function __construct( } /** + * Validate response schema definition for topic + * * @param string $responseSchema * @param string $topicName * @return void @@ -46,6 +48,12 @@ public function validateResponseSchemaType($responseSchema, $topicName) { try { $this->validateType($responseSchema); + } catch (\InvalidArgumentException $e) { + throw new \LogicException( + 'Response schema definition has service class with wrong annotated methods', + $e->getCode(), + $e + ); } catch (\Exception $e) { throw new \LogicException( sprintf( @@ -59,6 +67,8 @@ public function validateResponseSchemaType($responseSchema, $topicName) } /** + * Validate request schema definition for topic + * * @param string $requestSchema * @param string $topicName * @return void @@ -67,6 +77,12 @@ public function validateRequestSchemaType($requestSchema, $topicName) { try { $this->validateType($requestSchema); + } catch (\InvalidArgumentException $e) { + throw new \LogicException( + 'Request schema definition has service class with wrong annotated methods', + $e->getCode(), + $e + ); } catch (\Exception $e) { throw new \LogicException( sprintf( @@ -80,6 +96,8 @@ public function validateRequestSchemaType($requestSchema, $topicName) } /** + * Validate service method specified in the definition of handler + * * @param string $serviceName * @param string $methodName * @param string $handlerName @@ -109,6 +127,7 @@ public function validateResponseHandlersType($serviceName, $methodName, $handler * @param string $typeName * @return $this * @throws \Exception In case when type is invalid + * @throws \InvalidArgumentException if methods don't have annotation */ protected function validateType($typeName) { diff --git a/lib/internal/Magento/Framework/Composer/Remove.php b/lib/internal/Magento/Framework/Composer/Remove.php index b7a9a20333d..b7cea7769a1 100644 --- a/lib/internal/Magento/Framework/Composer/Remove.php +++ b/lib/internal/Magento/Framework/Composer/Remove.php @@ -46,7 +46,7 @@ public function remove(array $packages) [ 'command' => 'remove', 'packages' => $packages, - '--no-update' => true, + '--no-update-with-dependencies' => true, ] ); } diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 441da10253a..86266ec23fe 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -515,6 +515,7 @@ protected function _checkDdlTransaction($sql) /** * Special handling for PDO query(). + * * All bind parameter names must begin with ':'. * * @param string|\Magento\Framework\DB\Select $sql The SQL statement with placeholders. @@ -595,6 +596,7 @@ protected function _query($sql, $bind = []) /** * Special handling for PDO query(). + * * All bind parameter names must begin with ':'. * * @param string|\Magento\Framework\DB\Select $sql The SQL statement with placeholders. @@ -615,9 +617,10 @@ public function query($sql, $bind = []) } /** + * Allows multiple queries + * * Allows multiple queries -- to safeguard against SQL injection, USE CAUTION and verify that input * cannot be tampered with. - * * Special handling for PDO query(). * All bind parameter names must begin with ':'. * @@ -1278,6 +1281,7 @@ public function getForeignKeysTree() /** * Modify tables, used for upgrade process + * * Change columns definitions, reset foreign keys, change tables comments and engines. * * The value of each array element is an associative array @@ -1469,9 +1473,9 @@ public function select() * * Method revrited for handle empty arrays in value param * - * @param string $text The text with a placeholder. - * @param mixed $value The value to quote. - * @param string $type OPTIONAL SQL datatype + * @param string $text The text with a placeholder. + * @param mixed $value The value to quote. + * @param string $type OPTIONAL SQL datatype * @param integer $count OPTIONAL count of placeholders to replace * @return string An SQL-safe quoted value placed into the orignal text. */ @@ -1514,6 +1518,7 @@ protected function _getCacheId($tableKey, $ddlType) /** * Load DDL data from cache + * * Return false if cache does not exists * * @param string $tableCacheKey the table cache key @@ -1568,7 +1573,8 @@ public function saveDdlCache($tableCacheKey, $ddlType, $data) /** * Reset cached DDL data from cache - * if table name is null - reset all cached DDL data + * + * If table name is null - reset all cached DDL data * * @param string $tableName * @param string $schemaName OPTIONAL @@ -1605,6 +1611,7 @@ public function resetDdlCache($tableName = null, $schemaName = null) /** * Disallow DDL caching + * * @return $this */ public function disallowDdlCache() @@ -1615,6 +1622,7 @@ public function disallowDdlCache() /** * Allow DDL caching + * * @return $this */ public function allowDdlCache() @@ -1675,9 +1683,10 @@ public function describeTable($tableName, $schemaName = null) /** * Format described column to definition, ready to be added to ddl table. + * * Return array with keys: name, type, length, options, comment * - * @param array $columnData + * @param array $columnData * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -1892,6 +1901,7 @@ public function changeTableComment($tableName, $comment, $schemaName = null) /** * Inserts a table row with specified data + * * Special for Zero values to identity column * * @param string $table @@ -2782,6 +2792,7 @@ public function dropIndex($tableName, $keyName, $schemaName = null) /** * Add new Foreign Key to table + * * If Foreign Key with same name is exist - it will be deleted * * @param string $fkName @@ -3016,6 +3027,7 @@ protected function _transformStringSqlCondition($conditionKey, $value) /** * Prepare value for save in column + * * Return converted to column data type value * * @param array $column the column describe array @@ -3139,6 +3151,8 @@ public function getIfNullSql($expression, $value = 0) } /** + * Generates case SQL fragment + * * Generate fragment of SQL, that check value against multiple condition cases * and return different result depends on them * @@ -3163,6 +3177,7 @@ public function getCaseSql($valueName, $casesResults, $defaultValue = null) /** * Generate fragment of SQL, that combine together (concatenate) the results from data array + * * All arguments in data must be quoted * * @param string[] $data @@ -3177,6 +3192,7 @@ public function getConcatSql(array $data, $separator = null) /** * Generate fragment of SQL that returns length of character string + * * The string argument must be quoted * * @param string $string @@ -3188,6 +3204,8 @@ public function getLengthSql($string) } /** + * Generate least SQL fragment + * * Generate fragment of SQL, that compare with two or more arguments, and returns the smallest * (minimum-valued) argument * All arguments in data must be quoted @@ -3201,6 +3219,8 @@ public function getLeastSql(array $data) } /** + * Generate greatest SQL fragment + * * Generate fragment of SQL, that compare with two or more arguments, and returns the largest * (maximum-valued) argument * All arguments in data must be quoted @@ -3371,6 +3391,7 @@ public function getTriggerName($tableName, $time, $event) /** * Retrieve valid index name + * * Check index name length and allowed symbols * * @param string $tableName @@ -3400,6 +3421,7 @@ public function getIndexName($tableName, $fields, $indexType = '') /** * Retrieve valid foreign key name + * * Check foreign key name length and allowed symbols * * @param string $priTableName @@ -3668,6 +3690,7 @@ public function supportStraightJoin() /** * Adds order by random to select object + * * Possible using integer field for optimization * * @param Select $select @@ -3845,6 +3868,7 @@ public function getPrimaryKeyName($tableName, $schemaName = null) /** * Parse text size + * * Returns max allowed size if value great it * * @param string|int $size @@ -3857,13 +3881,13 @@ protected function _parseTextSize($size) switch ($last) { case 'k': - $size = intval($size) * 1024; + $size = (int)$size * 1024; break; case 'm': - $size = intval($size) * 1024 * 1024; + $size = (int)$size * 1024 * 1024; break; case 'g': - $size = intval($size) * 1024 * 1024 * 1024; + $size = (int)$size * 1024 * 1024 * 1024; break; } @@ -3874,11 +3898,12 @@ protected function _parseTextSize($size) return Table::MAX_TEXT_SIZE; } - return intval($size); + return (int)$size; } /** * Converts fetched blob into raw binary PHP data. + * * The MySQL drivers do it nice, no processing required. * * @param mixed $value diff --git a/lib/internal/Magento/Framework/DB/Helper.php b/lib/internal/Magento/Framework/DB/Helper.php index 4cb8304cded..4d4eac62978 100644 --- a/lib/internal/Magento/Framework/DB/Helper.php +++ b/lib/internal/Magento/Framework/DB/Helper.php @@ -8,6 +8,9 @@ namespace Magento\Framework\DB; +/** + * DataBase Helper + */ class Helper extends \Magento\Framework\DB\Helper\AbstractHelper { /** @@ -52,7 +55,7 @@ protected function _prepareOrder(\Magento\Framework\DB\Select $select, $autoRese * Field can be with 'dot' delimiter. * * @param string $field - * @param bool $reverse OPTIONAL + * @param bool $reverse OPTIONAL * @return string */ protected function _truncateAliasName($field, $reverse = false) @@ -143,6 +146,7 @@ protected function _prepareHaving(\Magento\Framework\DB\Select $select, $autoRes } /** + * Assemble limit * * @param string $query * @param int $limitCount @@ -153,12 +157,12 @@ protected function _prepareHaving(\Magento\Framework\DB\Select $select, $autoRes protected function _assembleLimit($query, $limitCount, $limitOffset, $columnList = []) { if ($limitCount !== null) { - $limitCount = intval($limitCount); + $limitCount = (int)$limitCount; if ($limitCount <= 0) { //throw new \Exception("LIMIT argument count={$limitCount} is not valid"); } - $limitOffset = intval($limitOffset); + $limitOffset = (int)$limitOffset; if ($limitOffset < 0) { //throw new \Exception("LIMIT argument offset={$limitOffset} is not valid"); } diff --git a/lib/internal/Magento/Framework/DB/Query.php b/lib/internal/Magento/Framework/DB/Query.php index 04ac2006b5e..36aab54df39 100644 --- a/lib/internal/Magento/Framework/DB/Query.php +++ b/lib/internal/Magento/Framework/DB/Query.php @@ -142,7 +142,7 @@ public function getSize() $sql = $this->getSelectCountSql(); $this->totalRecords = $this->getConnection()->fetchOne($sql, $this->bindParams); } - return intval($this->totalRecords); + return (int)$this->totalRecords; } /** diff --git a/lib/internal/Magento/Framework/DB/Query/BatchIterator.php b/lib/internal/Magento/Framework/DB/Query/BatchIterator.php index 7b4406fd9ad..6485973ec35 100644 --- a/lib/internal/Magento/Framework/DB/Query/BatchIterator.php +++ b/lib/internal/Magento/Framework/DB/Query/BatchIterator.php @@ -88,6 +88,8 @@ public function __construct( } /** + * Returns current select + * * @return Select */ public function current() @@ -101,6 +103,8 @@ public function current() } /** + * Returns next select + * * @return Select */ public function next() @@ -121,6 +125,8 @@ public function next() } /** + * Returns key + * * @return int */ public function key() @@ -129,6 +135,8 @@ public function key() } /** + * Returns is valid + * * @return bool */ public function valid() @@ -137,6 +145,8 @@ public function valid() } /** + * Rewind + * * @return void */ public function rewind() @@ -165,7 +175,7 @@ private function calculateBatchSize(Select $select) ); $row = $this->connection->fetchRow($wrapperSelect); $this->minValue = $row['max']; - return intval($row['cnt']); + return (int)$row['cnt']; } /** diff --git a/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php b/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php index 5176c85cfa7..e2ef4ae530a 100644 --- a/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php +++ b/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php @@ -201,7 +201,7 @@ private function initSelectObject() ); $row = $this->connection->fetchRow($wrapperSelect); - $this->totalItemCount = intval($row['cnt']); + $this->totalItemCount = (int)$row['cnt']; } $rangeField = is_array($this->rangeField) ? $this->rangeField : [$this->rangeField]; diff --git a/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php b/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php index 9e875e485c6..3c5cb65e898 100644 --- a/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php +++ b/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php @@ -41,19 +41,19 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function __toString() { $sql = $this->sql; - $count = intval($this->count); + $count = (int)$this->count; if ($count <= 0) { /** @see Zend_Db_Adapter_Exception */ #require_once 'Zend/Db/Adapter/Exception.php'; throw new \Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); } - $offset = intval($this->offset); + $offset = (int)$this->offset; if ($offset < 0) { /** @see Zend_Db_Adapter_Exception */ #require_once 'Zend/Db/Adapter/Exception.php'; diff --git a/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php b/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php index b6426a8d263..9a881ccf515 100644 --- a/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php +++ b/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php @@ -28,7 +28,7 @@ public function __construct(InterpreterInterface $itemInterpreter) } /** - * {@inheritdoc} + * @inheritdoc * @return array * @throws \InvalidArgumentException */ @@ -90,11 +90,11 @@ private function compareItems($firstItemKey, $secondItemKey, $indexedItems) $firstValue = 0; $secondValue = 0; if (isset($firstItem['sortOrder'])) { - $firstValue = intval($firstItem['sortOrder']); + $firstValue = (int)$firstItem['sortOrder']; } if (isset($secondItem['sortOrder'])) { - $secondValue = intval($secondItem['sortOrder']); + $secondValue = (int)$secondItem['sortOrder']; } if ($firstValue == $secondValue) { diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index 71ec8c1aa83..9c789e81913 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -285,7 +285,7 @@ public function getSize() if ($this->_totalRecords === null) { $this->_totalRecords = count($this->getItems()); } - return intval($this->_totalRecords); + return (int)$this->_totalRecords; } /** @@ -487,8 +487,7 @@ public function clear() } /** - * Walk through the collection and run model method or external callback - * with optional arguments + * Walk through the collection and run model method or external callback with optional arguments * * Returns array with results of callback for each item * @@ -743,7 +742,7 @@ public function toArray($arrRequiredFields = []) /** * Convert items array to array for select options * - * return items array + * Return items array * array( * $index => array( * 'value' => mixed @@ -772,6 +771,8 @@ protected function _toOptionArray($valueField = 'id', $labelField = 'name', $add } /** + * Returns option array + * * @return array */ public function toOptionArray() @@ -780,6 +781,8 @@ public function toOptionArray() } /** + * Returns options hash + * * @return array */ public function toOptionHash() @@ -790,12 +793,12 @@ public function toOptionHash() /** * Convert items array to hash for select options * - * return items hash + * Return items hash * array($value => $label) * - * @param string $valueField - * @param string $labelField - * @return array + * @param string $valueField + * @param string $labelField + * @return array */ protected function _toOptionHash($valueField = 'id', $labelField = 'name') { @@ -809,8 +812,8 @@ protected function _toOptionHash($valueField = 'id', $labelField = 'name') /** * Retrieve item by id * - * @param mixed $idValue - * @return \Magento\Framework\DataObject + * @param mixed $idValue + * @return \Magento\Framework\DataObject */ public function getItemById($idValue) { @@ -879,6 +882,8 @@ public function hasFlag($flag) } /** + * Sleep handler + * * @return string[] * @since 100.0.11 */ diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Editor.php b/lib/internal/Magento/Framework/Data/Form/Element/Editor.php index 39a0479f754..c438edf3aa9 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Editor.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Editor.php @@ -50,6 +50,8 @@ public function __construct( } /** + * Returns buttons translation + * * @return array */ protected function getButtonTranslations() @@ -64,6 +66,8 @@ protected function getButtonTranslations() } /** + * Returns JS config + * * @return bool|string * @throws \InvalidArgumentException */ @@ -80,8 +84,9 @@ protected function getJsonConfig() /** * Fetch config options from plugin. If $key is passed, return only that option key's value + * * @param string $pluginName - * @param null $key + * @param string|null $key * @return mixed all options or single option if $key is passed; null if nonexistent */ public function getPluginConfigOptions($pluginName, $key = null) @@ -101,13 +106,15 @@ public function getPluginConfigOptions($pluginName, $key = null) $pluginOptions = $plugins[$pluginArrIndex]['options']; if ($key !== null) { - return isset($pluginOptions[$key]) ? $pluginOptions[$key] : null; + return $pluginOptions[$key] ?? null; } else { return $pluginOptions; } } /** + * Returns element html + * * @return string * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -200,6 +207,8 @@ public function getElementHtml() } /** + * Returns theme + * * @return mixed */ public function getTheme() @@ -343,6 +352,8 @@ protected function _checkPluginButtonOptions($pluginOptions) } /** + * Convert options + * * Convert options by replacing template constructions ( like {{var_name}} ) * with data from this element object * @@ -389,6 +400,7 @@ protected function _getButtonHtml($data) /** * Wraps Editor HTML into div if 'use_container' config option is set to true + * * If 'no_display' config option is set to true, the div will be invisible * * @param string $html HTML code to wrap @@ -463,6 +475,8 @@ public function isHidden() } /** + * Is Toggle Button Visible + * * @return bool */ protected function isToggleButtonVisible() diff --git a/lib/internal/Magento/Framework/Exception/LocalizedException.php b/lib/internal/Magento/Framework/Exception/LocalizedException.php index 0b1d5f1998b..977c69db77b 100644 --- a/lib/internal/Magento/Framework/Exception/LocalizedException.php +++ b/lib/internal/Magento/Framework/Exception/LocalizedException.php @@ -11,6 +11,8 @@ use Magento\Framework\Phrase\Renderer\Placeholder; /** + * Localized exception + * * @api */ class LocalizedException extends \Exception @@ -33,7 +35,7 @@ class LocalizedException extends \Exception public function __construct(Phrase $phrase, \Exception $cause = null, $code = 0) { $this->phrase = $phrase; - parent::__construct($phrase->render(), intval($code), $cause); + parent::__construct($phrase->render(), (int)$code, $cause); } /** diff --git a/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php b/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php index 67c2d17abe2..574ef9faf74 100644 --- a/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php +++ b/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php @@ -313,6 +313,6 @@ public function getNumber() if (!$this->isNumeric()) { $this->prev(); } - return floatval($value); + return (float)$value; } } diff --git a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAlreadyExistsException.php b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAlreadyExistsException.php new file mode 100644 index 00000000000..d25707b2ca3 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAlreadyExistsException.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Exception; + +use GraphQL\Error\ClientAware; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Framework\Phrase; + +/** + * Class GraphQlAlreadyExistsException + */ +class GraphQlAlreadyExistsException extends AlreadyExistsException implements ClientAware +{ + /** + * Describing a category of the error + */ + const EXCEPTION_CATEGORY = 'graphql-already-exists'; + + /** + * @var boolean + */ + private $isSafe; + + /** + * @param Phrase $phrase + * @param \Exception $cause + * @param int $code + * @param boolean $isSafe + */ + public function __construct(Phrase $phrase, \Exception $cause = null, $code = 0, $isSafe = true) + { + $this->isSafe = $isSafe; + parent::__construct($phrase, $cause, $code); + } + + /** + * @inheritdoc + */ + public function isClientSafe(): bool + { + return $this->isSafe; + } + + /** + * @inheritdoc + */ + public function getCategory(): string + { + return self::EXCEPTION_CATEGORY; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAuthenticationException.php b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAuthenticationException.php new file mode 100644 index 00000000000..4df74bbf332 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAuthenticationException.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Exception; + +use GraphQL\Error\ClientAware; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Phrase; + +/** + * Class GraphQlAuthenticationException + */ +class GraphQlAuthenticationException extends AuthenticationException implements ClientAware +{ + /** + * Describing a category of the error + */ + const EXCEPTION_CATEGORY = 'graphql-authentication'; + + /** + * @var boolean + */ + private $isSafe; + + /** + * @param Phrase $phrase + * @param \Exception $cause + * @param int $code + * @param boolean $isSafe + */ + public function __construct(Phrase $phrase, \Exception $cause = null, $code = 0, $isSafe = true) + { + $this->isSafe = $isSafe; + parent::__construct($phrase, $cause, $code); + } + + /** + * @inheritdoc + */ + public function isClientSafe(): bool + { + return $this->isSafe; + } + + /** + * @inheritdoc + */ + public function getCategory(): string + { + return self::EXCEPTION_CATEGORY; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Entity/DefaultMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Entity/DefaultMapper.php index 7e3f2ac6db6..cacc1f9e28c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Entity/DefaultMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Entity/DefaultMapper.php @@ -26,10 +26,10 @@ public function __construct(array $map = []) } /** - * {@inheritDoc} + * @inheritdoc */ public function getMappedTypes(string $entityName) : array { - return isset($this->map[$entityName]) ? $this->map[$entityName] : []; + return $this->map[$entityName] ?? []; } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/DefaultDataMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/DefaultDataMapper.php index f7b39ba6420..f560fcb0de7 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/DefaultDataMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/DefaultDataMapper.php @@ -26,10 +26,10 @@ public function __construct(array $map) } /** - * {@inheritDoc} + * @inheritdoc */ public function getMappedEnums(string $enumName) : array { - return isset($this->map[$enumName]) ? $this->map[$enumName] : []; + return $this->map[$enumName] ?? []; } } diff --git a/lib/internal/Magento/Framework/HTTP/Client/Curl.php b/lib/internal/Magento/Framework/HTTP/Client/Curl.php index 788439aa4ff..d1dfafdeb2a 100644 --- a/lib/internal/Magento/Framework/HTTP/Client/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Client/Curl.php @@ -161,6 +161,7 @@ public function removeHeader($name) /** * Authorization: Basic header + * * Login credentials support * * @param string $login username @@ -209,6 +210,7 @@ public function setCookies($cookies) /** * Clear cookies + * * @return void */ public function removeCookies() @@ -293,6 +295,7 @@ public function getCookies() /** * Get cookies array with details * (domain, expire time etc) + * * @return array */ public function getCookiesFull() @@ -327,6 +330,7 @@ public function getCookiesFull() /** * Get response status code + * * @see lib\Magento\Framework\HTTP\Client#getStatus() * * @return int @@ -345,6 +349,7 @@ public function getStatus() * @param string $method * @param string $uri * @param array|string $params - use $params as a string in case of JSON or XML POST request. + * * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -410,6 +415,7 @@ protected function makeRequest($method, $uri, $params = []) /** * Throw error exception + * * @param string $string * @return void * @throws \Exception @@ -435,7 +441,7 @@ protected function parseHeaders($ch, $data) if (count($line) != 3) { $this->doError("Invalid response line returned from server: " . $data); } - $this->_responseStatus = intval($line[1]); + $this->_responseStatus = (int)$line[1]; } else { $name = $value = ''; $out = explode(": ", trim($data), 2); @@ -474,6 +480,7 @@ protected function curlOption($name, $value) /** * Set curl options array directly + * * @param array $arr * @return void */ @@ -484,6 +491,7 @@ protected function curlOptions($arr) /** * Set CURL options overrides array + * * @param array $arr * @return void */ diff --git a/lib/internal/Magento/Framework/HTTP/Client/Socket.php b/lib/internal/Magento/Framework/HTTP/Client/Socket.php index d229baa5dd4..eba182b98b9 100644 --- a/lib/internal/Magento/Framework/HTTP/Client/Socket.php +++ b/lib/internal/Magento/Framework/HTTP/Client/Socket.php @@ -12,9 +12,8 @@ namespace Magento\Framework\HTTP\Client; /** - * @SuppressWarnings(PHPMD.UnusedPrivateField) - */ -/** + * Socket client + * * @SuppressWarnings(PHPMD.UnusedPrivateField) */ class Socket implements \Magento\Framework\HTTP\ClientInterface @@ -134,6 +133,7 @@ public function disconnect() /** * Set headers from hash + * * @param array $headers * @return void */ @@ -167,6 +167,7 @@ public function removeHeader($name) /** * Authorization: Basic header + * * Login credentials support * * @param string $login username @@ -235,8 +236,7 @@ public function get($uri) } /** - * Set host, port from full url - * and return relative url + * Set host, port from full url and return relative url * * @param string $uri ex. http://google.com/index.php?a=b * @return string ex. /index.php?a=b @@ -330,6 +330,7 @@ public function getCookies() /** * Get cookies array with details * (domain, expire time etc) + * * @return array */ public function getCookiesFull() @@ -424,7 +425,7 @@ protected function processResponse() if (count($line) != 3) { return $this->doError("Invalid response line returned from server: " . $responseLine); } - $this->_responseStatus = intval($line[1]); + $this->_responseStatus = (int)$line[1]; $this->processResponseHeaders(); $this->processRedirect(); @@ -444,6 +445,7 @@ protected function processRedirect() /** * Get response status code + * * @see \Magento\Framework\HTTP\Client#getStatus() * * @return int @@ -494,6 +496,7 @@ protected function makeRequest($method, $uri, $params = []) /** * Throw error exception + * * @param string $string * @return void * @throws \Exception @@ -505,6 +508,7 @@ public function doError($string) /** * Convert headers hash to string + * * @param array $append * @return string */ diff --git a/lib/internal/Magento/Framework/Image.php b/lib/internal/Magento/Framework/Image.php index 5dc1969559f..b3867c0197b 100644 --- a/lib/internal/Magento/Framework/Image.php +++ b/lib/internal/Magento/Framework/Image.php @@ -262,7 +262,7 @@ public function instruction() public function setImageBackgroundColor($color) { /** @noinspection PhpUndefinedFieldInspection */ - $this->_adapter->imageBackgroundColor = intval($color); + $this->_adapter->imageBackgroundColor = (int)$color; } /** diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 7d4026af6c0..9297ca25928 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -6,6 +6,11 @@ namespace Magento\Framework\Interception\Code\Generator; +/** + * Class Interceptor + ˚* + * @package Magento\Framework\Interception\Code\Generator + */ class Interceptor extends \Magento\Framework\Code\Generator\EntityAbstract { /** @@ -14,6 +19,8 @@ class Interceptor extends \Magento\Framework\Code\Generator\EntityAbstract const ENTITY_TYPE = 'interceptor'; /** + * Returns default result class name + * * @param string $modelClassName * @return string */ @@ -102,10 +109,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) $parameters[] = $this->_getMethodParameterInfo($parameter); } - $returnType = $method->getReturnType(); - $returnTypeValue = $returnType - ? ($returnType->allowsNull() ? '?' : '') .$returnType->getName() - : null; + $returnTypeValue = $this->getReturnTypeValue($method->getReturnType()); $methodInfo = [ 'name' => ($method->returnsReference() ? '& ' : '') . $method->getName(), 'parameters' => $parameters, @@ -137,6 +141,8 @@ protected function _getMethodInfo(\ReflectionMethod $method) } /** + * Return parameters list + * * @param array $parameters * @return string */ @@ -175,14 +181,16 @@ protected function _generateCode() } else { $this->_classGenerator->setExtendedClass($typeName); } - $this->_classGenerator->addTrait('\\'. \Magento\Framework\Interception\Interceptor::class); - $interfaces[] = '\\'. \Magento\Framework\Interception\InterceptorInterface::class; + $this->_classGenerator->addTrait('\\' . \Magento\Framework\Interception\Interceptor::class); + $interfaces[] = '\\' . \Magento\Framework\Interception\InterceptorInterface::class; $this->_classGenerator->setImplementedInterfaces($interfaces); return parent::_generateCode(); } /** - * {@inheritdoc} + * Validates data + * + * @return bool */ protected function _validateData() { @@ -205,4 +213,22 @@ protected function _validateData() } return $result; } + + /** + * Returns return type + * + * @param mixed $returnType + * @return null|string + */ + private function getReturnTypeValue($returnType): ?string + { + $returnTypeValue = null; + if ($returnType) { + $returnTypeValue = ($returnType->allowsNull() ? '?' : ''); + $returnTypeValue .= ($returnType->getName() === 'self') + ? $this->getSourceClassName() + : $returnType->getName(); + } + return $returnTypeValue; + } } diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php index 731db30bada..596640480c8 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php @@ -120,12 +120,12 @@ public function testGetTransport($templateType, $messageType, $bodyText, $templa ->with($this->equalTo('Email Subject')) ->willReturnSelf(); - $this->messageMock->expects($this->exactly(intval($messageType == MessageInterface::TYPE_TEXT))) + $this->messageMock->expects($this->exactly((int)($messageType == MessageInterface::TYPE_TEXT))) ->method('setBodyText') ->with($this->equalTo($bodyText)) ->willReturnSelf(); - $this->messageMock->expects($this->exactly(intval($messageType == MessageInterface::TYPE_HTML))) + $this->messageMock->expects($this->exactly((int)($messageType == MessageInterface::TYPE_HTML))) ->method('setBodyHtml') ->with($this->equalTo($bodyText)) ->willReturnSelf(); diff --git a/lib/internal/Magento/Framework/MessageQueue/Config.php b/lib/internal/Magento/Framework/MessageQueue/Config.php index e29b5d06bee..9a925e1417c 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Config.php +++ b/lib/internal/Magento/Framework/MessageQueue/Config.php @@ -30,18 +30,16 @@ public function __construct(Config\Data $queueConfigData) } /** - * {@inheritdoc} + * @inheritdoc */ public function getExchangeByTopic($topicName) { $publisherConfig = $this->getPublisherConfigByTopic($topicName); - return isset($publisherConfig[ConfigInterface::PUBLISHER_EXCHANGE]) - ? $publisherConfig[ConfigInterface::PUBLISHER_EXCHANGE] - : null; + return $publisherConfig[ConfigInterface::PUBLISHER_EXCHANGE] ?? null; } /** - * {@inheritdoc} + * @inheritdoc */ public function getQueuesByTopic($topic) { @@ -67,7 +65,7 @@ public function getQueuesByTopic($topic) } /** - * {@inheritdoc} + * @inheritdoc */ public function getConnectionByTopic($topic) { @@ -76,13 +74,11 @@ public function getConnectionByTopic($topic) } catch (\Magento\Framework\Exception\LocalizedException $e) { return null; } - return isset($publisherConfig[ConfigInterface::PUBLISHER_CONNECTION]) - ? $publisherConfig[ConfigInterface::PUBLISHER_CONNECTION] - : null; + return $publisherConfig[ConfigInterface::PUBLISHER_CONNECTION] ?? null; } /** - * {@inheritdoc} + * @inheritdoc */ public function getConnectionByConsumer($consumer) { @@ -98,7 +94,7 @@ public function getConnectionByConsumer($consumer) } /** - * {@inheritdoc} + * @inheritdoc */ public function getMessageSchemaType($topic) { @@ -109,7 +105,7 @@ public function getMessageSchemaType($topic) } /** - * {@inheritdoc} + * @inheritdoc */ public function getConsumerNames() { @@ -118,7 +114,7 @@ public function getConsumerNames() } /** - * {@inheritdoc} + * @inheritdoc */ public function getConsumer($name) { @@ -127,7 +123,7 @@ public function getConsumer($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getBinds() { @@ -135,7 +131,7 @@ public function getBinds() } /** - * {@inheritdoc} + * @inheritdoc */ public function getPublishers() { @@ -143,7 +139,7 @@ public function getPublishers() } /** - * {@inheritdoc} + * @inheritdoc */ public function getConsumers() { @@ -151,7 +147,7 @@ public function getConsumers() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTopic($name) { @@ -159,7 +155,7 @@ public function getTopic($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getPublisher($name) { @@ -167,7 +163,7 @@ public function getPublisher($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function getResponseQueueName($topicName) { diff --git a/lib/internal/Magento/Framework/MessageQueue/Config/CompositeReader.php b/lib/internal/Magento/Framework/MessageQueue/Config/CompositeReader.php index 10e0a6f6019..86e83d678c0 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Config/CompositeReader.php +++ b/lib/internal/Magento/Framework/MessageQueue/Config/CompositeReader.php @@ -69,10 +69,10 @@ function ($firstItem, $secondItem) { $firstValue = 0; $secondValue = 0; if (isset($firstItem['sortOrder'])) { - $firstValue = intval($firstItem['sortOrder']); + $firstValue = (int)$firstItem['sortOrder']; } if (isset($secondItem['sortOrder'])) { - $secondValue = intval($secondItem['sortOrder']); + $secondValue = (int)$secondItem['sortOrder']; } return $firstValue <=> $secondValue; } diff --git a/lib/internal/Magento/Framework/MessageQueue/Config/Reader/Xml/CompositeConverter.php b/lib/internal/Magento/Framework/MessageQueue/Config/Reader/Xml/CompositeConverter.php index b8286c33dca..0184f720b3b 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Config/Reader/Xml/CompositeConverter.php +++ b/lib/internal/Magento/Framework/MessageQueue/Config/Reader/Xml/CompositeConverter.php @@ -43,7 +43,7 @@ public function __construct(array $converters) } /** - * {@inheritdoc} + * @inheritdoc */ public function convert($source) { @@ -68,10 +68,10 @@ function ($firstItem, $secondItem) { $firstValue = 0; $secondValue = 0; if (isset($firstItem['sortOrder'])) { - $firstValue = intval($firstItem['sortOrder']); + $firstValue = (int)$firstItem['sortOrder']; } if (isset($secondItem['sortOrder'])) { - $secondValue = intval($secondItem['sortOrder']); + $secondValue = (int)$secondItem['sortOrder']; } return $firstValue <=> $secondValue; } diff --git a/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/Env/Reader.php b/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/Env/Reader.php index aa318ba5f19..a75aa1ea8ea 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/Env/Reader.php +++ b/lib/internal/Magento/Framework/MessageQueue/Consumer/Config/Env/Reader.php @@ -33,8 +33,6 @@ public function __construct(\Magento\Framework\MessageQueue\Config\Reader\Env $e public function read($scope = null) { $configData = $this->envConfig->read($scope); - return isset($configData[\Magento\Framework\MessageQueue\Config\Reader\Env::ENV_CONSUMERS]) - ? $configData[\Magento\Framework\MessageQueue\Config\Reader\Env::ENV_CONSUMERS] - : []; + return $configData[\Magento\Framework\MessageQueue\Config\Reader\Env::ENV_CONSUMERS] ?? []; } } diff --git a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php index 2e0120ad7ea..448a8a3db74 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php +++ b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/Consumer/Config/XsdTest.php @@ -10,15 +10,26 @@ class XsdTest extends \PHPUnit\Framework\TestCase /** * @var string */ - protected $_schemaFile; + private $schemaFile; + /** + * @var string + */ + private $schemaQueueFile; + + /** + * Set up. + * + * @return void + */ protected function setUp() { if (!function_exists('libxml_set_external_entity_loader')) { $this->markTestSkipped('Skipped on HHVM. Will be fixed in MAGETWO-45033'); } $urnResolver = new \Magento\Framework\Config\Dom\UrnResolver(); - $this->_schemaFile = $urnResolver->getRealPath('urn:magento:framework-message-queue:etc/consumer.xsd'); + $this->schemaFile = $urnResolver->getRealPath('urn:magento:framework-message-queue:etc/consumer.xsd'); + $this->schemaQueueFile = $urnResolver->getRealPath('urn:magento:framework-message-queue:etc/queue.xsd'); } /** @@ -29,13 +40,13 @@ protected function setUp() public function testExemplarXml($fixtureXml, array $expectedErrors) { $validationState = $this->createMock(\Magento\Framework\Config\ValidationStateInterface::class); - $validationState->expects($this->any()) + $validationState->expects($this->atLeastOnce()) ->method('isValidationRequired') ->willReturn(true); $messageFormat = '%message%'; $dom = new \Magento\Framework\Config\Dom($fixtureXml, $validationState, [], null, null, $messageFormat); $actualErrors = []; - $actualResult = $dom->validate($this->_schemaFile, $actualErrors); + $actualResult = $dom->validate($this->schemaFile, $actualErrors); $this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid."); $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match."); } @@ -125,4 +136,106 @@ public function exemplarXmlDataProvider() ]; // @codingStandardsIgnoreEnd } + + /** + * @param string $fixtureXml + * @param array $expectedErrors + * @dataProvider exemplarQueueXmlDataProvider + */ + public function testExemplarQueueXml($fixtureXml, array $expectedErrors) + { + $validationState = $this->createMock(\Magento\Framework\Config\ValidationStateInterface::class); + $validationState->expects($this->atLeastOnce()) + ->method('isValidationRequired') + ->willReturn(true); + $messageFormat = '%message%'; + $dom = new \Magento\Framework\Config\Dom($fixtureXml, $validationState, [], null, null, $messageFormat); + $actualErrors = []; + $actualResult = $dom->validate($this->schemaQueueFile, $actualErrors); + $this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid."); + $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match."); + } + + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function exemplarQueueXmlDataProvider() + { + // @codingStandardsIgnoreStart + return [ + 'valid' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="5"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5"/> + </broker> + </config>', + [], + ], + 'invalid handler format' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClass_One1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="5"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handler_Method2" consumerInstance="consumerClass2" maxMessages="5"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'handler': [facet 'pattern'] The value 'handlerClass_One1::handlerMethod1' is not accepted by the pattern '[a-zA-Z0-9\\\\]+::[a-zA-Z0-9]+'.", + "Element 'queue', attribute 'handler': 'handlerClass_One1::handlerMethod1' is not a valid value of the atomic type 'handlerType'.", + "Element 'queue', attribute 'handler': [facet 'pattern'] The value 'handlerClassOne2::handler_Method2' is not accepted by the pattern '[a-zA-Z0-9\\\\]+::[a-zA-Z0-9]+'.", + "Element 'queue', attribute 'handler': 'handlerClassOne2::handler_Method2' is not a valid value of the atomic type 'handlerType'.", + ], + ], + 'invalid instance format' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumer_Class1" maxMessages="5"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass_2" maxMessages="5"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'consumerInstance': [facet 'pattern'] The value 'consumer_Class1' is not accepted by the pattern '[a-zA-Z0-9\\\\]+'.", + "Element 'queue', attribute 'consumerInstance': 'consumer_Class1' is not a valid value of the atomic type 'instanceType'.", + "Element 'queue', attribute 'consumerInstance': [facet 'pattern'] The value 'consumerClass_2' is not accepted by the pattern '[a-zA-Z0-9\\\\]+'.", + "Element 'queue', attribute 'consumerInstance': 'consumerClass_2' is not a valid value of the atomic type 'instanceType'.", + ], + ], + 'invalid maxMessages format' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="ABC"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'maxMessages': 'ABC' is not a valid value of the atomic type 'xs:integer'.", + ], + ], + 'unexpected element' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="2"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5"/> + <unexpected name="queue2"/> + </broker> + </config>', + [ + "Element 'unexpected': This element is not expected. Expected is ( queue ).", + ], + ], + 'unexpected attribute' => [ + '<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue.xsd"> + <broker topic="asd" > + <queue name="queue1" consumer="consumer1" handler="handlerClassOne1::handlerMethod1" consumerInstance="consumerClass1" maxMessages="2"/> + <queue name="queue2" consumer="consumer2" handler="handlerClassOne2::handlerMethod2" consumerInstance="consumerClass2" maxMessages="5" unexpected="unexpected"/> + </broker> + </config>', + [ + "Element 'queue', attribute 'unexpected': The attribute 'unexpected' is not allowed.", + ], + ], + ]; + // @codingStandardsIgnoreEnd + } } diff --git a/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd b/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd index 4b326d637f2..bede197ec34 100644 --- a/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd +++ b/lib/internal/Magento/Framework/MessageQueue/etc/queue_base.xsd @@ -41,7 +41,7 @@ </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z\\]+" /> + <xs:pattern value="[a-zA-Z0-9\\]+" /> <xs:minLength value="4" /> </xs:restriction> </xs:simpleType> @@ -53,7 +53,7 @@ </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z\\]+::[a-zA-Z]+" /> + <xs:pattern value="[a-zA-Z0-9\\]+::[a-zA-Z0-9]+" /> <xs:minLength value="5" /> </xs:restriction> </xs:simpleType> diff --git a/lib/internal/Magento/Framework/Module/Plugin/DbStatusValidator.php b/lib/internal/Magento/Framework/Module/Plugin/DbStatusValidator.php index 30b2a370bcb..96f9674704e 100644 --- a/lib/internal/Magento/Framework/Module/Plugin/DbStatusValidator.php +++ b/lib/internal/Magento/Framework/Module/Plugin/DbStatusValidator.php @@ -121,7 +121,7 @@ private function getGroupedDbVersionErrors() (array)$allDbVersionErrors, function ($carry, $item) { if ($item[DbVersionInfo::KEY_CURRENT] === 'none' - || $item[DbVersionInfo::KEY_CURRENT] < $item[DbVersionInfo::KEY_REQUIRED] + || version_compare($item[DbVersionInfo::KEY_CURRENT], $item[DbVersionInfo::KEY_REQUIRED], '<') ) { $carry['version_too_low'][] = $item; } else { diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php index 96595bc7a07..efa1b4be60c 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php @@ -5,8 +5,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\ObjectManager\Code\Generator; +/** + * Class Proxy + * + * @package Magento\Framework\ObjectManager\Code\Generator + */ class Proxy extends \Magento\Framework\Code\Generator\EntityAbstract { /** @@ -20,6 +26,8 @@ class Proxy extends \Magento\Framework\Code\Generator\EntityAbstract const NON_INTERCEPTABLE_INTERFACE = \Magento\Framework\ObjectManager\NoninterceptableInterface::class; /** + * Returns default result class name + * * @param string $modelClassName * @return string */ @@ -112,13 +120,16 @@ protected function _getClassMethods() $reflectionClass = new \ReflectionClass($this->getSourceClassName()); $publicMethods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC); foreach ($publicMethods as $method) { - if (!($method->isConstructor() || + if (!( + $method->isConstructor() || $method->isFinal() || $method->isStatic() || - $method->isDestructor()) && !in_array( - $method->getName(), - ['__sleep', '__wakeup', '__clone'] - ) + $method->isDestructor() + ) + && !in_array( + $method->getName(), + ['__sleep', '__wakeup', '__clone'] + ) ) { $methods[] = $this->_getMethodInfo($method); } @@ -128,6 +139,8 @@ protected function _getClassMethods() } /** + * Generates code + * * @return string */ protected function _generateCode() @@ -160,10 +173,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) $parameters[] = $this->_getMethodParameterInfo($parameter); } - $returnType = $method->getReturnType(); - $returnTypeValue = $returnType - ? ($returnType->allowsNull() ? '?' : '') .$returnType->getName() - : null; + $returnTypeValue = $this->getReturnTypeValue($method->getReturnType()); $methodInfo = [ 'name' => $method->getName(), 'parameters' => $parameters, @@ -237,11 +247,13 @@ protected function _getMethodBody( } return ($withoutReturn ? '' : 'return ') - .'$this->_getSubject()->' . $methodCall . ';'; + . '$this->_getSubject()->' . $methodCall . ';'; } /** - * {@inheritdoc} + * Validates data + * + * @return bool */ protected function _validateData() { @@ -259,4 +271,22 @@ protected function _validateData() } return $result; } + + /** + * Returns return type + * + * @param mixed $returnType + * @return null|string + */ + private function getReturnTypeValue($returnType): ?string + { + $returnTypeValue = null; + if ($returnType) { + $returnTypeValue = ($returnType->allowsNull() ? '?' : ''); + $returnTypeValue .= ($returnType->getName() === 'self') + ? $this->getSourceClassName() + : $returnType->getName(); + } + return $returnTypeValue; + } } diff --git a/lib/internal/Magento/Framework/Reflection/MethodsMap.php b/lib/internal/Magento/Framework/Reflection/MethodsMap.php index 944cd8771ee..6b0ddfbfc21 100644 --- a/lib/internal/Magento/Framework/Reflection/MethodsMap.php +++ b/lib/internal/Magento/Framework/Reflection/MethodsMap.php @@ -94,6 +94,8 @@ public function getMethodReturnType($typeName, $methodName) * 'validatePassword' => 'boolean' * ] * </pre> + * @throws \InvalidArgumentException if methods don't have annotation + * @throws \ReflectionException for missing DocBock or invalid reflection class */ public function getMethodsMap($interfaceName) { @@ -148,6 +150,8 @@ public function getMethodParams($serviceClassName, $serviceMethodName) * * @param string $interfaceName * @return array + * @throws \ReflectionException for missing DocBock or invalid reflection class + * @throws \InvalidArgumentException if methods don't have annotation */ private function getMethodMapViaReflection($interfaceName) { diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Db/MySQL/Definition/Columns/Integer.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Db/MySQL/Definition/Columns/Integer.php index 07ba03d0331..1d08c5bea4e 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Db/MySQL/Definition/Columns/Integer.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Db/MySQL/Definition/Columns/Integer.php @@ -76,8 +76,8 @@ public function __construct( } /** - * @param \Magento\Framework\Setup\Declaration\Schema\Dto\Columns\Integer $column * @inheritdoc + * @param \Magento\Framework\Setup\Declaration\Schema\Dto\Columns\Integer $column */ public function toDefinition(ElementInterface $column) { @@ -89,7 +89,7 @@ public function toDefinition(ElementInterface $column) $this->unsigned->toDefinition($column), $this->nullable->toDefinition($column), $column->getDefault() !== null ? - sprintf('DEFAULT %s', (string) intval($column->getDefault())) : '', + sprintf('DEFAULT %s', (string) (int)$column->getDefault()) : '', $this->identity->toDefinition($column), $this->comment->toDefinition($column) ); diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Diff/DiffInterface.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Diff/DiffInterface.php index 4b6e3555256..139e9b90268 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Diff/DiffInterface.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Diff/DiffInterface.php @@ -7,9 +7,6 @@ namespace Magento\Framework\Setup\Declaration\Schema\Diff; use Magento\Framework\Setup\Declaration\Schema\Dto\ElementInterface; -use Magento\Framework\Setup\Declaration\Schema\Dto\Schema; -use Magento\Framework\Setup\Declaration\Schema\ElementHistory; -use Magento\Framework\Setup\Declaration\Schema\Request; /** * DiffInterface is type of classes, that holds all information @@ -39,9 +36,9 @@ public function getAll(); /** * Register operation. * - * @param ElementInterface|object $dtoObject - * @param string $operation - * @param ElementInterface $oldDtoObject + * @param ElementInterface|object $dtoObject + * @param string $operation + * @param ElementInterface $oldDtoObject * @return void */ public function register( diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Real.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Real.php index cdb740ea0a9..e0728b9a34f 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Real.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Real.php @@ -39,7 +39,7 @@ class Real implements FactoryInterface * Constructor. * * @param ObjectManagerInterface $objectManager - * @param string $className + * @param string $className */ public function __construct( ObjectManagerInterface $objectManager, @@ -50,7 +50,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function create(array $data) { @@ -63,7 +63,7 @@ public function create(array $data) } if (isset($data['default'])) { - $data['default'] = floatval($data['default']); + $data['default'] = (float)$data['default']; } return $this->objectManager->create($this->className, $data); diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Schema.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Schema.php index 3e68b985283..fbbe188d127 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Schema.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Schema.php @@ -63,14 +63,15 @@ public function addTable(Table $table) /** * Retrieve table by it name. + * * Return false if table is not present in schema. * - * @param $name + * @param string $name * @return bool|Table */ public function getTableByName($name) { $name = $this->resourceConnection->getTableName($name); - return isset($this->tables[$name]) ? $this->tables[$name] : false; + return $this->tables[$name] ?? false; } } diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Table.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Table.php index 4f020b1a032..b4e1e978ea6 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Table.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Table.php @@ -87,11 +87,11 @@ class Table extends GenericElement implements * @param string $engine * @param string $charset * @param string $collation + * @param string $onCreate * @param string|null $comment * @param array $columns * @param array $indexes * @param array $constraints - * @param string $onCreate * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -123,6 +123,7 @@ public function __construct( /** * Return different table constraints. + * * It can be constraint like unique key or reference to another table, etc * * @return Constraint[] @@ -133,12 +134,14 @@ public function getConstraints() } /** + * Returns constraint by name + * * @param string $name * @return Constraint | bool */ public function getConstraintByName($name) { - return isset($this->constraints[$name]) ? $this->constraints[$name] : false; + return $this->constraints[$name] ?? false; } /** @@ -160,6 +163,8 @@ public function getReferenceConstraints() } /** + * Returns primary constraint + * * As primary constraint always have one name * and can be only one for table * it name is allocated into it constraint @@ -168,9 +173,7 @@ public function getReferenceConstraints() */ public function getPrimaryConstraint() { - return isset($this->constraints[Internal::PRIMARY_NAME]) ? - $this->constraints[Internal::PRIMARY_NAME] : - false; + return $this->constraints[Internal::PRIMARY_NAME] ?? false; } /** @@ -191,16 +194,19 @@ public function getInternalConstraints() : array } /** + * Returns index by name + * * @param string $name * @return Index | bool */ public function getIndexByName($name) { - return isset($this->indexes[$name]) ? $this->indexes[$name] : false; + return $this->indexes[$name] ?? false; } /** * Return all columns. + * * Note, table always must have columns * * @return Column[] @@ -231,6 +237,8 @@ public function getResource() } /** + * Add constraints + * * This is workaround, as any DTO object couldnt be changed after instantiation. * However there is case, when we have 2 tables with constraints in different tables, * that depends to each other table. So we need to setup DTO first and only then pass @@ -280,6 +288,7 @@ public function getColumnByName($nameOrId) /** * Retrieve elements by specific type + * * Allowed types: columns, constraints, indexes... * * @param string $type @@ -295,6 +304,8 @@ public function getElementsByType($type) } /** + * Add indexes + * * This is workaround, as any DTO object couldnt be changed after instantiation. * However there is case, when we depends on column definition we need modify our indexes * @@ -314,6 +325,8 @@ public function getElementType() } /** + * Returns engine name + * * @return string */ public function getEngine(): string @@ -356,6 +369,8 @@ public function getCollation() : string } /** + * Returns name without prefix + * * @return string */ public function getNameWithoutPrefix(): string @@ -364,6 +379,8 @@ public function getNameWithoutPrefix(): string } /** + * Returns comment + * * @return null|string */ public function getComment() diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Request.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Request.php deleted file mode 100644 index 2ffe29cc769..00000000000 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Request.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Framework\Setup\Declaration\Schema; - -/** - * CLI or Ui params transport object. - */ -class Request -{ - /** - * Option to enable dump functionality for safe mode. - */ - const DUMP_ENABLE_OPTIONS = "dump_enable"; - - /** - * @var bool - */ - private $dumpEnable = false; - - /** - * Constructor. - * - * @param array $request - */ - public function __construct(array $request) - { - if (isset($request[static::DUMP_ENABLE_OPTIONS])) { - $this->dumpEnable = (bool) $request[static::DUMP_ENABLE_OPTIONS]; - } - } - - /** - * Check whether dump is enabled. - * - * @return boolean - */ - public function isDumpEnabled() - { - return $this->dumpEnable; - } -} diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/RequestFactory.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/RequestFactory.php deleted file mode 100644 index 8f76815d4dc..00000000000 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/RequestFactory.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Framework\Setup\Declaration\Schema; - -use Zend\Di\Di; - -/** - * Request Factory. - */ -class RequestFactory -{ - /** - * @var Di - */ - private $zendDi; - - /** - * @var string - */ - private static $instanceName = Request::class; - - /** - * RequestFactory constructor. - * - * @param Di $zendDi - */ - public function __construct(Di $zendDi) - { - $this->zendDi = $zendDi; - } - - /** - * Create request object with requestOptions params. - * - * @param array $requestOptions - * @return Request - */ - public function create(array $requestOptions = []) - { - return $this->zendDi->newInstance( - self::$instanceName, - ['request' => $requestOptions] - ); - } -} diff --git a/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/TextBlobDefinition.php b/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/TextBlobDefinition.php index 10e3b70e61b..9e8d63b26cf 100644 --- a/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/TextBlobDefinition.php +++ b/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/TextBlobDefinition.php @@ -15,6 +15,7 @@ class TextBlobDefinition implements DefinitionConverterInterface { /** * Parse text size. + * * Returns max allowed size if value great it. * * @param string|int $size @@ -27,13 +28,13 @@ private function parseTextSize($size) switch ($last) { case 'k': - $size = intval($size) * 1024; + $size = (int)$size * 1024; break; case 'm': - $size = intval($size) * 1024 * 1024; + $size = (int)$size * 1024 * 1024; break; case 'g': - $size = intval($size) * 1024 * 1024 * 1024; + $size = (int)$size * 1024 * 1024 * 1024; break; } @@ -44,7 +45,7 @@ private function parseTextSize($size) return Table::MAX_TEXT_SIZE; } - return intval($size); + return (int)$size; } /** diff --git a/lib/internal/Magento/Framework/System/Ftp.php b/lib/internal/Magento/Framework/System/Ftp.php index 8bf898965cb..ad0673ff5d7 100644 --- a/lib/internal/Magento/Framework/System/Ftp.php +++ b/lib/internal/Magento/Framework/System/Ftp.php @@ -32,7 +32,7 @@ protected function checkConnected() } /** - * ftp_mkdir wrapper + * Wrapper for ftp_mkdir * * @param string $name * @return string the newly created directory name on success or <b>FALSE</b> on error. @@ -127,7 +127,7 @@ public function validateConnectionString($string) public function connect($string, $timeout = 900) { $params = $this->validateConnectionString($string); - $port = isset($params['port']) ? intval($params['port']) : 21; + $port = isset($params['port']) ? (int)$params['port'] : 21; $this->_conn = ftp_connect($params['host'], $port, $timeout); @@ -147,7 +147,7 @@ public function connect($string, $timeout = 900) } /** - * ftp_fput wrapper + * Wrapper for ftp_fput * * @param string $remoteFile * @param resource $handle @@ -162,7 +162,7 @@ public function fput($remoteFile, $handle, $mode = FTP_BINARY, $startPos = 0) } /** - * ftp_put wrapper + * Wrapper for ftp_put * * @param string $remoteFile * @param string $localFile @@ -188,7 +188,7 @@ public function getcwd() if (empty($data[1])) { return false; } - if (intval($data[0]) != 257) { + if ((int)$data[0] != 257) { return false; } $out = trim($data[1], '"'); @@ -199,7 +199,7 @@ public function getcwd() } /** - * ftp_raw wrapper + * Wrapper for ftp_raw * * @param string $cmd * @return array The server's response as an array of strings. @@ -272,7 +272,7 @@ public function download($remote, $local, $ftpMode = FTP_BINARY) } /** - * ftp_pasv wrapper + * Wrapper for ftp_pasv * * @param bool $pasv * @return bool @@ -296,7 +296,7 @@ public function close() } /** - * ftp_chmod wrapper + * Wrapper for ftp_chmod * * @param int $mode * @param string $remoteFile @@ -309,7 +309,7 @@ public function chmod($mode, $remoteFile) } /** - * ftp_chdir wrapper + * Wrapper for ftp_chdir * * @param string $dir * @return bool @@ -321,7 +321,7 @@ public function chdir($dir) } /** - * ftp_cdup wrapper + * Wrapper for ftp_cdup * * @return bool */ @@ -332,7 +332,7 @@ public function cdup() } /** - * ftp_get wrapper + * Wrapper for ftp_get * * @param string $localFile * @param string $remoteFile @@ -349,7 +349,7 @@ public function get($localFile, $remoteFile, $fileMode = FTP_BINARY, $resumeOffs } /** - * ftp_nlist wrapper + * Wrapper for ftp_nlist * * @param string $dir * @return bool @@ -362,7 +362,7 @@ public function nlist($dir = "/") } /** - * ftp_rawlist wrapper + * Wrapper for ftp_rawlist * * @param string $dir * @param bool $recursive diff --git a/lib/internal/Magento/Framework/Test/Unit/Communication/Config/ValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Communication/Config/ValidatorTest.php new file mode 100644 index 00000000000..55410af176a --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/Communication/Config/ValidatorTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Test\Unit\Communication\Config; + +use Magento\Framework\Communication\Config\Validator; +use Magento\Framework\Reflection\MethodsMap; +use Magento\Framework\Reflection\TypeProcessor; + +/** + * Unit test for \Magento\Framework\Communication\Config\Validator class + */ +class ValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var TypeProcessor|\PHPUnit_Framework_MockObject_MockObject + */ + protected $typeProcessor; + + /** + * @var MethodsMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $methodsMap; + + public function setUp() + { + $this->methodsMap = $this->createMock(MethodsMap::class); + + $this->methodsMap->expects(static::any()) + ->method('getMethodsMap') + ->will($this->throwException(new \InvalidArgumentException('message', 333))); + + $this->typeProcessor = $this->createMock(TypeProcessor::class); + $this->typeProcessor->expects(static::any()) + ->method('isTypeSimple') + ->willReturn(false); + + $this->typeProcessor->expects(static::any()) + ->method('isTypeSimple') + ->willReturn(false); + } + + /** + * @expectedException \LogicException + * @expectedExceptionCode 333 + * @expectedExceptionMessage Response schema definition has service class with wrong annotated methods + */ + public function testValidateResponseSchemaType() + { + /** @var Validator $validator */ + $validator = new Validator($this->typeProcessor, $this->methodsMap); + $validator->validateResponseSchemaType('123', '123'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionCode 333 + * @expectedExceptionMessage Request schema definition has service class with wrong annotated methods + */ + public function testValidateRequestSchemaType() + { + /** @var Validator $validator */ + $validator = new Validator($this->typeProcessor, $this->methodsMap); + $validator->validateRequestSchemaType('123', '123'); + } +} diff --git a/lib/internal/Magento/Framework/Test/Unit/Module/Plugin/DbStatusValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Module/Plugin/DbStatusValidatorTest.php index a52c49136d1..5027bfae606 100644 --- a/lib/internal/Magento/Framework/Test/Unit/Module/Plugin/DbStatusValidatorTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/Module/Plugin/DbStatusValidatorTest.php @@ -157,29 +157,29 @@ public static function beforeDispatchOutOfDateWithErrorsDataProvider() [ DbVersionInfo::KEY_MODULE => 'Magento_Module4', DbVersionInfo::KEY_TYPE => 'data', - DbVersionInfo::KEY_CURRENT => '1.0.1', - DbVersionInfo::KEY_REQUIRED => '1.0.0' + DbVersionInfo::KEY_CURRENT => '1.0.10', + DbVersionInfo::KEY_REQUIRED => '1.0.9' ], ], 'expectedMessage' => "Please update your modules: " . "Run \"composer install\" from the Magento root directory.\n" . "The following modules are outdated:\n" . "Magento_Module3 schema: code version - 1.0.0, database version - 2.0.0\n" - . "Magento_Module4 data: code version - 1.0.0, database version - 1.0.1", + . "Magento_Module4 data: code version - 1.0.9, database version - 1.0.10", ], 'some versions too high, some too low' => [ 'errors' => [ [ - DbVersionInfo::KEY_MODULE => 'Magento_Module1', + DbVersionInfo::KEY_MODULE => 'Magento_Module2', DbVersionInfo::KEY_TYPE => 'schema', - DbVersionInfo::KEY_CURRENT => '2.0.0', - DbVersionInfo::KEY_REQUIRED => '1.0.0' + DbVersionInfo::KEY_CURRENT => '1.9.0', + DbVersionInfo::KEY_REQUIRED => '1.12.0' ], [ - DbVersionInfo::KEY_MODULE => 'Magento_Module2', + DbVersionInfo::KEY_MODULE => 'Magento_Module1', DbVersionInfo::KEY_TYPE => 'schema', - DbVersionInfo::KEY_CURRENT => '1.0.0', - DbVersionInfo::KEY_REQUIRED => '2.0.0' + DbVersionInfo::KEY_CURRENT => '2.0.0', + DbVersionInfo::KEY_REQUIRED => '1.0.0' ], ], 'expectedMessage' => "Please update your modules: " 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 ab1bca9f454..43bfd46c119 100644 --- a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php +++ b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php @@ -104,13 +104,13 @@ protected function _toHtml() if ($this->isCurrent()) { $html = '<li class="nav item current">'; $html .= '<strong>' - . $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getLabel())) + . $this->escapeHtml(__($this->getLabel())) . '</strong>'; $html .= '</li>'; } else { $html = '<li class="nav item' . $highlight . '"><a href="' . $this->escapeHtml($this->getHref()) . '"'; $html .= $this->getTitle() - ? ' title="' . $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getTitle())) . '"' + ? ' title="' . $this->escapeHtml(__($this->getTitle())) . '"' : ''; $html .= $this->getAttributesHtml() . '>'; @@ -118,7 +118,7 @@ protected function _toHtml() $html .= '<strong>'; } - $html .= $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getLabel())); + $html .= $this->escapeHtml(__($this->getLabel())); if ($this->getIsHighlighted()) { $html .= '</strong>'; diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php index 48a66b429eb..e472a9c9eff 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php @@ -104,7 +104,7 @@ class Context implements ContextInterface * @param Processor $processor * @param UiComponentFactory $uiComponentFactory * @param DataProviderInterface|null $dataProvider - * @param null $namespace + * @param string|null $namespace * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -152,7 +152,7 @@ public function addComponentDefinition($name, array $config) } /** - * {@inheritdoc} + * @inheritdoc */ public function getComponentsDefinitions() { @@ -160,7 +160,7 @@ public function getComponentsDefinitions() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRenderEngine() { @@ -168,7 +168,7 @@ public function getRenderEngine() } /** - * {@inheritdoc} + * @inheritdoc */ public function getNamespace() { @@ -176,7 +176,7 @@ public function getNamespace() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAcceptType() { @@ -184,7 +184,7 @@ public function getAcceptType() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRequestParams() { @@ -192,7 +192,7 @@ public function getRequestParams() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRequestParam($key, $defaultValue = null) { @@ -200,7 +200,7 @@ public function getRequestParam($key, $defaultValue = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getFiltersParams() { @@ -208,7 +208,7 @@ public function getFiltersParams() } /** - * {@inheritdoc} + * @inheritdoc */ public function getFilterParam($key, $defaultValue = null) { @@ -217,7 +217,7 @@ public function getFilterParam($key, $defaultValue = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getDataProvider() { @@ -225,7 +225,7 @@ public function getDataProvider() } /** - * {@inheritdoc} + * @inheritdoc */ public function getDataSourceData(UiComponentInterface $component) { @@ -250,7 +250,7 @@ public function getDataSourceData(UiComponentInterface $component) } /** - * {@inheritdoc} + * @inheritdoc */ public function getPageLayout() { @@ -258,7 +258,7 @@ public function getPageLayout() } /** - * {@inheritdoc} + * @inheritdoc */ public function addButtons(array $buttons, UiComponentInterface $component) { @@ -297,14 +297,14 @@ public function addButtons(array $buttons, UiComponentInterface $component) */ public function sortButtons(array $itemA, array $itemB) { - $sortOrderA = isset($itemA['sort_order']) ? intval($itemA['sort_order']) : 0; - $sortOrderB = isset($itemB['sort_order']) ? intval($itemB['sort_order']) : 0; + $sortOrderA = isset($itemA['sort_order']) ? (int)$itemA['sort_order'] : 0; + $sortOrderB = isset($itemB['sort_order']) ? (int)$itemB['sort_order'] : 0; return $sortOrderA - $sortOrderB; } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function addHtmlBlocks(array $htmlBlocks, UiComponentInterface $component) @@ -336,7 +336,7 @@ protected function setAcceptType() } /** - * {@inheritdoc} + * @inheritdoc */ public function setDataProvider(DataProviderInterface $dataProvider) { @@ -344,7 +344,7 @@ public function setDataProvider(DataProviderInterface $dataProvider) } /** - * {@inheritdoc} + * @inheritdoc */ public function getUrl($route = '', $params = []) { @@ -370,7 +370,7 @@ protected function prepareDataSource(array & $data, UiComponentInterface $compon } /** - * {@inheritdoc} + * @inheritdoc */ public function getProcessor() { @@ -378,7 +378,7 @@ public function getProcessor() } /** - * {@inheritdoc} + * @inheritdoc */ public function getUiComponentFactory() { diff --git a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php index b124c417148..6fc5b284b75 100644 --- a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php @@ -317,7 +317,7 @@ public function apiShutdownFunction() protected function _saveFatalErrorReport($reportData) { $this->directoryWrite->create('report/api'); - $reportId = abs(intval(microtime(true) * random_int(100, 1000))); + $reportId = abs((int)(microtime(true) * random_int(100, 1000))); $this->directoryWrite->writeFile('report/api/' . $reportId, $this->serializer->serialize($reportData)); return $reportId; } diff --git a/lib/web/css/source/lib/_typography.less b/lib/web/css/source/lib/_typography.less index 07128abbf7f..62529fe08d1 100644 --- a/lib/web/css/source/lib/_typography.less +++ b/lib/web/css/source/lib/_typography.less @@ -37,7 +37,7 @@ } // Rem line height -.lib-line-height(@heightValue) when not (@heightValue = false) and not (ispercentage(@heightValue)) { +.lib-line-height(@heightValue) when not (@heightValue = false) and not (@heightValue = normal) and not (ispercentage(@heightValue)) { .lib-font-size-value(@heightValue); .lib-css(line-height, @fontValue); } @@ -46,6 +46,10 @@ .lib-css(line-height, @heightValue); } +.lib-line-height(@heightValue) when (@heightValue = normal) { + .lib-css(line-height, @heightValue); +} + .lib-wrap-words() { overflow-wrap: break-word; word-wrap: break-word; diff --git a/lib/web/fotorama/fotorama.js b/lib/web/fotorama/fotorama.js index 89cb315b0d2..a16e437c0d7 100644 --- a/lib/web/fotorama/fotorama.js +++ b/lib/web/fotorama/fotorama.js @@ -1951,10 +1951,10 @@ fotoramaVersion = '4.6.4'; if (e.keyCode === 27) { catched = true; that.cancelFullScreen(); - } else if ((e.shiftKey && e.keyCode === 32 && allowKey('space')) || (e.keyCode === 37 && allowKey('left')) || (e.keyCode === 38 && allowKey('up') && $(':focus').attr('data-gallery-role'))) { + } else if ((e.shiftKey && e.keyCode === 32 && allowKey('space')) || (!e.altKey && !e.metaKey && e.keyCode === 37 && allowKey('left')) || (e.keyCode === 38 && allowKey('up') && $(':focus').attr('data-gallery-role'))) { that.longPress.progress(); index = '<'; - } else if ((e.keyCode === 32 && allowKey('space')) || (e.keyCode === 39 && allowKey('right')) || (e.keyCode === 40 && allowKey('down') && $(':focus').attr('data-gallery-role'))) { + } else if ((e.keyCode === 32 && allowKey('space')) || (!e.altKey && !e.metaKey && e.keyCode === 39 && allowKey('right')) || (e.keyCode === 40 && allowKey('down') && $(':focus').attr('data-gallery-role'))) { that.longPress.progress(); index = '>'; } else if (e.keyCode === 36 && allowKey('home')) { @@ -2568,7 +2568,7 @@ fotoramaVersion = '4.6.4'; thisData.t > rightLimit : thisData.l > rightLimit; specialMeasures.w = thisData.w; - if (thisData.l + thisData.w < leftLimit + if ((opts.navdir !== 'vertical' && thisData.l + thisData.w < leftLimit) || exceedLimit || callFit(thisData.$img, specialMeasures)) return; @@ -3553,7 +3553,7 @@ fotoramaVersion = '4.6.4'; if ((result.moved || (toggleControlsFLAG && result.pos !== result.newPos && !result.control)) && result.$target[0] !== $fullscreenIcon[0]) { var index = getIndexByPos(result.newPos, measures.w, opts.margin, repositionIndex); - + that.show({ index: index, time: o_fade ? o_transitionDuration : result.time, diff --git a/lib/web/fotorama/fotorama.min.js b/lib/web/fotorama/fotorama.min.js index 1f9358e0469..9842f20cdc2 100644 --- a/lib/web/fotorama/fotorama.min.js +++ b/lib/web/fotorama/fotorama.min.js @@ -1 +1 @@ -fotoramaVersion="4.6.4";(function(window,document,location,$,undefined){"use strict";var _fotoramaClass="fotorama",_fullscreenClass="fotorama__fullscreen",wrapClass=_fotoramaClass+"__wrap",wrapCss2Class=wrapClass+"--css2",wrapCss3Class=wrapClass+"--css3",wrapVideoClass=wrapClass+"--video",wrapFadeClass=wrapClass+"--fade",wrapSlideClass=wrapClass+"--slide",wrapNoControlsClass=wrapClass+"--no-controls",wrapNoShadowsClass=wrapClass+"--no-shadows",wrapPanYClass=wrapClass+"--pan-y",wrapRtlClass=wrapClass+"--rtl",wrapOnlyActiveClass=wrapClass+"--only-active",wrapNoCaptionsClass=wrapClass+"--no-captions",wrapToggleArrowsClass=wrapClass+"--toggle-arrows",stageClass=_fotoramaClass+"__stage",stageFrameClass=stageClass+"__frame",stageFrameVideoClass=stageFrameClass+"--video",stageShaftClass=stageClass+"__shaft",grabClass=_fotoramaClass+"__grab",pointerClass=_fotoramaClass+"__pointer",arrClass=_fotoramaClass+"__arr",arrDisabledClass=arrClass+"--disabled",arrPrevClass=arrClass+"--prev",arrNextClass=arrClass+"--next",navClass=_fotoramaClass+"__nav",navWrapClass=navClass+"-wrap",navShaftClass=navClass+"__shaft",navShaftVerticalClass=navWrapClass+"--vertical",navShaftListClass=navWrapClass+"--list",navShafthorizontalClass=navWrapClass+"--horizontal",navDotsClass=navClass+"--dots",navThumbsClass=navClass+"--thumbs",navFrameClass=navClass+"__frame",fadeClass=_fotoramaClass+"__fade",fadeFrontClass=fadeClass+"-front",fadeRearClass=fadeClass+"-rear",shadowClass=_fotoramaClass+"__shadow",shadowsClass=shadowClass+"s",shadowsLeftClass=shadowsClass+"--left",shadowsRightClass=shadowsClass+"--right",shadowsTopClass=shadowsClass+"--top",shadowsBottomClass=shadowsClass+"--bottom",activeClass=_fotoramaClass+"__active",selectClass=_fotoramaClass+"__select",hiddenClass=_fotoramaClass+"--hidden",fullscreenClass=_fotoramaClass+"--fullscreen",fullscreenIconClass=_fotoramaClass+"__fullscreen-icon",errorClass=_fotoramaClass+"__error",loadingClass=_fotoramaClass+"__loading",loadedClass=_fotoramaClass+"__loaded",loadedFullClass=loadedClass+"--full",loadedImgClass=loadedClass+"--img",grabbingClass=_fotoramaClass+"__grabbing",imgClass=_fotoramaClass+"__img",imgFullClass=imgClass+"--full",thumbClass=_fotoramaClass+"__thumb",thumbArrLeft=thumbClass+"__arr--left",thumbArrRight=thumbClass+"__arr--right",thumbBorderClass=thumbClass+"-border",htmlClass=_fotoramaClass+"__html",videoContainerClass=_fotoramaClass+"-video-container",videoClass=_fotoramaClass+"__video",videoPlayClass=videoClass+"-play",videoCloseClass=videoClass+"-close",horizontalImageClass=_fotoramaClass+"_horizontal_ratio",verticalImageClass=_fotoramaClass+"_vertical_ratio",fotoramaSpinnerClass=_fotoramaClass+"__spinner",spinnerShowClass=fotoramaSpinnerClass+"--show";var JQUERY_VERSION=$&&$.fn.jquery.split(".");if(!JQUERY_VERSION||JQUERY_VERSION[0]<1||JQUERY_VERSION[0]==1&&JQUERY_VERSION[1]<8){throw"Fotorama requires jQuery 1.8 or later and will not run without it."}var _={};var Modernizr=function(window,document,undefined){var version="2.8.3",Modernizr={},docElement=document.documentElement,mod="modernizr",modElem=document.createElement(mod),mStyle=modElem.style,inputElem,toString={}.toString,prefixes=" -webkit- -moz- -o- -ms- ".split(" "),omPrefixes="Webkit Moz O ms",cssomPrefixes=omPrefixes.split(" "),domPrefixes=omPrefixes.toLowerCase().split(" "),tests={},inputs={},attrs={},classes=[],slice=classes.slice,featureName,injectElementWithStyles=function(rule,callback,nodes,testnames){var style,ret,node,docOverflow,div=document.createElement("div"),body=document.body,fakeBody=body||document.createElement("body");if(parseInt(nodes,10)){while(nodes--){node=document.createElement("div");node.id=testnames?testnames[nodes]:mod+(nodes+1);div.appendChild(node)}}style=["­",'<style id="s',mod,'">',rule,"</style>"].join("");div.id=mod;(body?div:fakeBody).innerHTML+=style;fakeBody.appendChild(div);if(!body){fakeBody.style.background="";fakeBody.style.overflow="hidden";docOverflow=docElement.style.overflow;docElement.style.overflow="hidden";docElement.appendChild(fakeBody)}ret=callback(div,rule);if(!body){fakeBody.parentNode.removeChild(fakeBody);docElement.style.overflow=docOverflow}else{div.parentNode.removeChild(div)}return!!ret},_hasOwnProperty={}.hasOwnProperty,hasOwnProp;if(!is(_hasOwnProperty,"undefined")&&!is(_hasOwnProperty.call,"undefined")){hasOwnProp=function(object,property){return _hasOwnProperty.call(object,property)}}else{hasOwnProp=function(object,property){return property in object&&is(object.constructor.prototype[property],"undefined")}}if(!Function.prototype.bind){Function.prototype.bind=function bind(that){var target=this;if(typeof target!="function"){throw new TypeError}var args=slice.call(arguments,1),bound=function(){if(this instanceof bound){var F=function(){};F.prototype=target.prototype;var self=new F;var result=target.apply(self,args.concat(slice.call(arguments)));if(Object(result)===result){return result}return self}else{return target.apply(that,args.concat(slice.call(arguments)))}};return bound}}function setCss(str){mStyle.cssText=str}function setCssAll(str1,str2){return setCss(prefixes.join(str1+";")+(str2||""))}function is(obj,type){return typeof obj===type}function contains(str,substr){return!!~(""+str).indexOf(substr)}function testProps(props,prefixed){for(var i in props){var prop=props[i];if(!contains(prop,"-")&&mStyle[prop]!==undefined){return prefixed=="pfx"?prop:true}}return false}function testDOMProps(props,obj,elem){for(var i in props){var item=obj[props[i]];if(item!==undefined){if(elem===false)return props[i];if(is(item,"function")){return item.bind(elem||obj)}return item}}return false}function testPropsAll(prop,prefixed,elem){var ucProp=prop.charAt(0).toUpperCase()+prop.slice(1),props=(prop+" "+cssomPrefixes.join(ucProp+" ")+ucProp).split(" ");if(is(prefixed,"string")||is(prefixed,"undefined")){return testProps(props,prefixed)}else{props=(prop+" "+domPrefixes.join(ucProp+" ")+ucProp).split(" ");return testDOMProps(props,prefixed,elem)}}tests["touch"]=function(){var bool;if("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch){bool=true}else{injectElementWithStyles(["@media (",prefixes.join("touch-enabled),("),mod,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(node){bool=node.offsetTop===9})}return bool};tests["csstransforms3d"]=function(){var ret=!!testPropsAll("perspective");if(ret&&"webkitPerspective"in docElement.style){injectElementWithStyles("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(node,rule){ret=node.offsetLeft===9&&node.offsetHeight===3})}return ret};tests["csstransitions"]=function(){return testPropsAll("transition")};for(var feature in tests){if(hasOwnProp(tests,feature)){featureName=feature.toLowerCase();Modernizr[featureName]=tests[feature]();classes.push((Modernizr[featureName]?"":"no-")+featureName)}}Modernizr.addTest=function(feature,test){if(typeof feature=="object"){for(var key in feature){if(hasOwnProp(feature,key)){Modernizr.addTest(key,feature[key])}}}else{feature=feature.toLowerCase();if(Modernizr[feature]!==undefined){return Modernizr}test=typeof test=="function"?test():test;if(typeof enableClasses!=="undefined"&&enableClasses){docElement.className+=" "+(test?"":"no-")+feature}Modernizr[feature]=test}return Modernizr};setCss("");modElem=inputElem=null;Modernizr._version=version;Modernizr._prefixes=prefixes;Modernizr._domPrefixes=domPrefixes;Modernizr._cssomPrefixes=cssomPrefixes;Modernizr.testProp=function(prop){return testProps([prop])};Modernizr.testAllProps=testPropsAll;Modernizr.testStyles=injectElementWithStyles;Modernizr.prefixed=function(prop,obj,elem){if(!obj){return testPropsAll(prop,"pfx")}else{return testPropsAll(prop,obj,elem)}};return Modernizr}(window,document);var fullScreenApi={ok:false,is:function(){return false},request:function(){},cancel:function(){},event:"",prefix:""},browserPrefixes="webkit moz o ms khtml".split(" ");if(typeof document.cancelFullScreen!="undefined"){fullScreenApi.ok=true}else{for(var i=0,il=browserPrefixes.length;i<il;i++){fullScreenApi.prefix=browserPrefixes[i];if(typeof document[fullScreenApi.prefix+"CancelFullScreen"]!="undefined"){fullScreenApi.ok=true;break}}}if(fullScreenApi.ok){fullScreenApi.event=fullScreenApi.prefix+"fullscreenchange";fullScreenApi.is=function(){switch(this.prefix){case"":return document.fullScreen;case"webkit":return document.webkitIsFullScreen;default:return document[this.prefix+"FullScreen"]}};fullScreenApi.request=function(el){return this.prefix===""?el.requestFullScreen():el[this.prefix+"RequestFullScreen"]()};fullScreenApi.cancel=function(el){return this.prefix===""?document.cancelFullScreen():document[this.prefix+"CancelFullScreen"]()}}function bez(coOrdArray){var encodedFuncName="bez_"+$.makeArray(arguments).join("_").replace(".","p");if(typeof $["easing"][encodedFuncName]!=="function"){var polyBez=function(p1,p2){var A=[null,null],B=[null,null],C=[null,null],bezCoOrd=function(t,ax){C[ax]=3*p1[ax];B[ax]=3*(p2[ax]-p1[ax])-C[ax];A[ax]=1-C[ax]-B[ax];return t*(C[ax]+t*(B[ax]+t*A[ax]))},xDeriv=function(t){return C[0]+t*(2*B[0]+3*A[0]*t)},xForT=function(t){var x=t,i=0,z;while(++i<14){z=bezCoOrd(x,0)-t;if(Math.abs(z)<.001)break;x-=z/xDeriv(x)}return x};return function(t){return bezCoOrd(xForT(t),1)}};$["easing"][encodedFuncName]=function(x,t,b,c,d){return c*polyBez([coOrdArray[0],coOrdArray[1]],[coOrdArray[2],coOrdArray[3]])(t/d)+b}}return encodedFuncName}var $WINDOW=$(window),$DOCUMENT=$(document),$HTML,$BODY,QUIRKS_FORCE=location.hash.replace("#","")==="quirks",TRANSFORMS3D=Modernizr.csstransforms3d,CSS3=TRANSFORMS3D&&!QUIRKS_FORCE,COMPAT=TRANSFORMS3D||document.compatMode==="CSS1Compat",FULLSCREEN=fullScreenApi.ok,MOBILE=navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),SLOW=!CSS3||MOBILE,MS_POINTER=navigator.msPointerEnabled,WHEEL="onwheel"in document.createElement("div")?"wheel":document.onmousewheel!==undefined?"mousewheel":"DOMMouseScroll",TOUCH_TIMEOUT=250,TRANSITION_DURATION=300,SCROLL_LOCK_TIMEOUT=1400,AUTOPLAY_INTERVAL=5e3,MARGIN=2,THUMB_SIZE=64,WIDTH=500,HEIGHT=333,STAGE_FRAME_KEY="$stageFrame",NAV_DOT_FRAME_KEY="$navDotFrame",NAV_THUMB_FRAME_KEY="$navThumbFrame",AUTO="auto",BEZIER=bez([.1,0,.25,1]),MAX_WIDTH=1200,thumbsPerSlide=1,OPTIONS={width:null,minwidth:null,maxwidth:"100%",height:null,minheight:null,maxheight:null,ratio:null,margin:MARGIN,nav:"dots",navposition:"bottom",navwidth:null,thumbwidth:THUMB_SIZE,thumbheight:THUMB_SIZE,thumbmargin:MARGIN,thumbborderwidth:MARGIN,allowfullscreen:false,transition:"slide",clicktransition:null,transitionduration:TRANSITION_DURATION,captions:true,startindex:0,loop:false,autoplay:false,stopautoplayontouch:true,keyboard:false,arrows:true,click:true,swipe:false,trackpad:false,shuffle:false,direction:"ltr",shadows:true,showcaption:true,navdir:"horizontal",navarrows:true,navtype:"thumbs"},KEYBOARD_OPTIONS={left:true,right:true,down:true,up:true,space:false,home:false,end:false};function noop(){}function minMaxLimit(value,min,max){return Math.max(isNaN(min)?-Infinity:min,Math.min(isNaN(max)?Infinity:max,value))}function readTransform(css,dir){return css.match(/ma/)&&css.match(/-?\d+(?!d)/g)[css.match(/3d/)?dir==="vertical"?13:12:dir==="vertical"?5:4]}function readPosition($el,dir){if(CSS3){return+readTransform($el.css("transform"),dir)}else{return+$el.css(dir==="vertical"?"top":"left").replace("px","")}}function getTranslate(pos,direction){var obj={};if(CSS3){switch(direction){case"vertical":obj.transform="translate3d(0, "+pos+"px,0)";break;case"list":break;default:obj.transform="translate3d("+pos+"px,0,0)";break}}else{direction==="vertical"?obj.top=pos:obj.left=pos}return obj}function getDuration(time){return{"transition-duration":time+"ms"}}function unlessNaN(value,alternative){return isNaN(value)?alternative:value}function numberFromMeasure(value,measure){return unlessNaN(+String(value).replace(measure||"px",""))}function numberFromPercent(value){return/%$/.test(value)?numberFromMeasure(value,"%"):undefined}function numberFromWhatever(value,whole){return unlessNaN(numberFromPercent(value)/100*whole,numberFromMeasure(value))}function measureIsValid(value){return(!isNaN(numberFromMeasure(value))||!isNaN(numberFromMeasure(value,"%")))&&value}function getPosByIndex(index,side,margin,baseIndex){return(index-(baseIndex||0))*(side+(margin||0))}function getIndexByPos(pos,side,margin,baseIndex){return-Math.round(pos/(side+(margin||0))-(baseIndex||0))}function bindTransitionEnd($el){var elData=$el.data();if(elData.tEnd)return;var el=$el[0],transitionEndEvent={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",msTransition:"MSTransitionEnd",transition:"transitionend"};addEvent(el,transitionEndEvent[Modernizr.prefixed("transition")],function(e){elData.tProp&&e.propertyName.match(elData.tProp)&&elData.onEndFn()});elData.tEnd=true}function afterTransition($el,property,fn,time){var ok,elData=$el.data();if(elData){elData.onEndFn=function(){if(ok)return;ok=true;clearTimeout(elData.tT);fn()};elData.tProp=property;clearTimeout(elData.tT);elData.tT=setTimeout(function(){elData.onEndFn()},time*1.5);bindTransitionEnd($el)}}function stop($el,pos){var dir=$el.navdir||"horizontal";if($el.length){var elData=$el.data();if(CSS3){$el.css(getDuration(0));elData.onEndFn=noop;clearTimeout(elData.tT)}else{$el.stop()}var lockedPos=getNumber(pos,function(){return readPosition($el,dir)});$el.css(getTranslate(lockedPos,dir));return lockedPos}}function getNumber(){var number;for(var _i=0,_l=arguments.length;_i<_l;_i++){number=_i?arguments[_i]():arguments[_i];if(typeof number==="number"){break}}return number}function edgeResistance(pos,edge){return Math.round(pos+(edge-pos)/1.5)}function getProtocol(){getProtocol.p=getProtocol.p||(location.protocol==="https:"?"https://":"http://");return getProtocol.p}function parseHref(href){var a=document.createElement("a");a.href=href;return a}function findVideoId(href,forceVideo){if(typeof href!=="string")return href;href=parseHref(href);var id,type;if(href.host.match(/youtube\.com/)&&href.search){id=href.search.split("v=")[1];if(id){var ampersandPosition=id.indexOf("&");if(ampersandPosition!==-1){id=id.substring(0,ampersandPosition)}type="youtube"}}else if(href.host.match(/youtube\.com|youtu\.be/)){id=href.pathname.replace(/^\/(embed\/|v\/)?/,"").replace(/\/.*/,"");type="youtube"}else if(href.host.match(/vimeo\.com/)){type="vimeo";id=href.pathname.replace(/^\/(video\/)?/,"").replace(/\/.*/,"")}if((!id||!type)&&forceVideo){id=href.href;type="custom"}return id?{id:id,type:type,s:href.search.replace(/^\?/,""),p:getProtocol()}:false}function getVideoThumbs(dataFrame,data,fotorama){var img,thumb,video=dataFrame.video;if(video.type==="youtube"){thumb=getProtocol()+"img.youtube.com/vi/"+video.id+"/default.jpg";img=thumb.replace(/\/default.jpg$/,"/hqdefault.jpg");dataFrame.thumbsReady=true}else if(video.type==="vimeo"){$.ajax({url:getProtocol()+"vimeo.com/api/v2/video/"+video.id+".json",dataType:"jsonp",success:function(json){dataFrame.thumbsReady=true;updateData(data,{img:json[0].thumbnail_large,thumb:json[0].thumbnail_small},dataFrame.i,fotorama)}})}else{dataFrame.thumbsReady=true}return{img:img,thumb:thumb}}function updateData(data,_dataFrame,i,fotorama){for(var _i=0,_l=data.length;_i<_l;_i++){var dataFrame=data[_i];if(dataFrame.i===i&&dataFrame.thumbsReady){var clear={videoReady:true};clear[STAGE_FRAME_KEY]=clear[NAV_THUMB_FRAME_KEY]=clear[NAV_DOT_FRAME_KEY]=false;fotorama.splice(_i,1,$.extend({},dataFrame,clear,_dataFrame));break}}}function getDataFromHtml($el){var data=[];function getDataFromImg($img,imgData,checkVideo){var $child=$img.children("img").eq(0),_imgHref=$img.attr("href"),_imgSrc=$img.attr("src"),_thumbSrc=$child.attr("src"),_video=imgData.video,video=checkVideo?findVideoId(_imgHref,_video===true):false;if(video){_imgHref=false}else{video=_video}getDimensions($img,$child,$.extend(imgData,{video:video,img:imgData.img||_imgHref||_imgSrc||_thumbSrc,thumb:imgData.thumb||_thumbSrc||_imgSrc||_imgHref}))}function getDimensions($img,$child,imgData){var separateThumbFLAG=imgData.thumb&&imgData.img!==imgData.thumb,width=numberFromMeasure(imgData.width||$img.attr("width")),height=numberFromMeasure(imgData.height||$img.attr("height"));$.extend(imgData,{width:width,height:height,thumbratio:getRatio(imgData.thumbratio||numberFromMeasure(imgData.thumbwidth||$child&&$child.attr("width")||separateThumbFLAG||width)/numberFromMeasure(imgData.thumbheight||$child&&$child.attr("height")||separateThumbFLAG||height))})}$el.children().each(function(){var $this=$(this),dataFrame=optionsToLowerCase($.extend($this.data(),{id:$this.attr("id")}));if($this.is("a, img")){getDataFromImg($this,dataFrame,true)}else if(!$this.is(":empty")){getDimensions($this,null,$.extend(dataFrame,{html:this,_html:$this.html()}))}else return;data.push(dataFrame)});return data}function isHidden(el){return el.offsetWidth===0&&el.offsetHeight===0}function isDetached(el){return!$.contains(document.documentElement,el)}function waitFor(test,fn,timeout,i){if(!waitFor.i){waitFor.i=1;waitFor.ii=[true]}i=i||waitFor.i;if(typeof waitFor.ii[i]==="undefined"){waitFor.ii[i]=true}if(test()){fn()}else{waitFor.ii[i]&&setTimeout(function(){waitFor.ii[i]&&waitFor(test,fn,timeout,i)},timeout||100)}return waitFor.i++}waitFor.stop=function(i){waitFor.ii[i]=false};function fit($el,measuresToFit){var elData=$el.data(),measures=elData.measures;if(measures&&(!elData.l||elData.l.W!==measures.width||elData.l.H!==measures.height||elData.l.r!==measures.ratio||elData.l.w!==measuresToFit.w||elData.l.h!==measuresToFit.h)){var height=minMaxLimit(measuresToFit.h,0,measures.height),width=height*measures.ratio;UTIL.setRatio($el,width,height);elData.l={W:measures.width,H:measures.height,r:measures.ratio,w:measuresToFit.w,h:measuresToFit.h}}return true}function setStyle($el,style){var el=$el[0];if(el.styleSheet){el.styleSheet.cssText=style}else{$el.html(style)}}function findShadowEdge(pos,min,max,dir){return min===max?false:dir==="vertical"?pos<=min?"top":pos>=max?"bottom":"top bottom":pos<=min?"left":pos>=max?"right":"left right"}function smartClick($el,fn,_options){_options=_options||{};$el.each(function(){var $this=$(this),thisData=$this.data(),startEvent;if(thisData.clickOn)return;thisData.clickOn=true;$.extend(touch($this,{onStart:function(e){startEvent=e;(_options.onStart||noop).call(this,e)},onMove:_options.onMove||noop,onTouchEnd:_options.onTouchEnd||noop,onEnd:function(result){if(result.moved)return;fn.call(this,startEvent)}}),{noMove:true})})}function div(classes,child){return'<div class="'+classes+'">'+(child||"")+"</div>"}function cls(className){return"."+className}function createVideoFrame(videoItem){var frame='<iframe src="'+videoItem.p+videoItem.type+".com/embed/"+videoItem.id+'" frameborder="0" allowfullscreen></iframe>';return frame}function shuffle(array){var l=array.length;while(l){var i=Math.floor(Math.random()*l--);var t=array[l];array[l]=array[i];array[i]=t}return array}function clone(array){return Object.prototype.toString.call(array)=="[object Array]"&&$.map(array,function(frame){return $.extend({},frame)})}function lockScroll($el,left,top){$el.scrollLeft(left||0).scrollTop(top||0)}function optionsToLowerCase(options){if(options){var opts={};$.each(options,function(key,value){opts[key.toLowerCase()]=value});return opts}}function getRatio(_ratio){if(!_ratio)return;var ratio=+_ratio;if(!isNaN(ratio)){return ratio}else{ratio=_ratio.split("/");return+ratio[0]/+ratio[1]||undefined}}function addEvent(el,e,fn,bool){if(!e)return;el.addEventListener?el.addEventListener(e,fn,!!bool):el.attachEvent("on"+e,fn)}function validateRestrictions(position,restriction){if(position>restriction.max){position=restriction.max}else{if(position<restriction.min){position=restriction.min}}return position}function validateSlidePos(opt,navShaftTouchTail,guessIndex,offsetNav,$guessNavFrame,$navWrap,dir){var position,size,wrapSize;if(dir==="horizontal"){size=opt.thumbwidth;wrapSize=$navWrap.width()}else{size=opt.thumbheight;wrapSize=$navWrap.height()}if((size+opt.margin)*(guessIndex+1)>=wrapSize-offsetNav){if(dir==="horizontal"){position=-$guessNavFrame.position().left}else{position=-$guessNavFrame.position().top}}else{if((size+opt.margin)*guessIndex<=Math.abs(offsetNav)){if(dir==="horizontal"){position=-$guessNavFrame.position().left+wrapSize-(size+opt.margin)}else{position=-$guessNavFrame.position().top+wrapSize-(size+opt.margin)}}else{position=offsetNav}}position=validateRestrictions(position,navShaftTouchTail);return position||0}function elIsDisabled(el){return!!el.getAttribute("disabled")}function disableAttr(FLAG,disable){if(disable){return{disabled:FLAG}}else{return{tabindex:FLAG*-1+"",disabled:FLAG}}}function addEnterUp(el,fn){addEvent(el,"keyup",function(e){elIsDisabled(el)||e.keyCode==13&&fn.call(el,e)})}function addFocus(el,fn){addEvent(el,"focus",el.onfocusin=function(e){fn.call(el,e)},true)}function stopEvent(e,stopPropagation){e.preventDefault?e.preventDefault():e.returnValue=false;stopPropagation&&e.stopPropagation&&e.stopPropagation()}function stubEvent($el,eventType){var isIOS=/ip(ad|hone|od)/i.test(window.navigator.userAgent);if(isIOS&&eventType==="touchend"){$el.on("touchend",function(e){$DOCUMENT.trigger("mouseup",e)})}$el.on(eventType,function(e){stopEvent(e,true);return false})}function getDirectionSign(forward){return forward?">":"<"}var UTIL=function(){function setRatioClass($el,wh,ht){var rateImg=wh/ht;if(rateImg<=1){$el.parent().removeClass(horizontalImageClass);$el.parent().addClass(verticalImageClass)}else{$el.parent().removeClass(verticalImageClass);$el.parent().addClass(horizontalImageClass)}}function setThumbAttr($frame,value,searchAttr){var attr=searchAttr;if(!$frame.attr(attr)&&$frame.attr(attr)!==undefined){$frame.attr(attr,value)}if($frame.find("["+attr+"]").length){$frame.find("["+attr+"]").each(function(){$(this).attr(attr,value)})}}function isExpectedCaption(frameItem,isExpected,undefined){var expected=false,frameExpected;frameItem.showCaption===undefined||frameItem.showCaption===true?frameExpected=true:frameExpected=false;if(!isExpected){return false}if(frameItem.caption&&frameExpected){expected=true}return expected}return{setRatio:setRatioClass,setThumbAttr:setThumbAttr,isExpectedCaption:isExpectedCaption}}(UTIL||{},jQuery);function slide($el,options){var elData=$el.data(),elPos=Math.round(options.pos),onEndFn=function(){if(elData&&elData.sliding){elData.sliding=false}(options.onEnd||noop)()};if(typeof options.overPos!=="undefined"&&options.overPos!==options.pos){elPos=options.overPos}var translate=$.extend(getTranslate(elPos,options.direction),options.width&&{width:options.width},options.height&&{height:options.height});if(elData&&elData.sliding){elData.sliding=true}if(CSS3){$el.css($.extend(getDuration(options.time),translate));if(options.time>10){afterTransition($el,"transform",onEndFn,options.time)}else{onEndFn()}}else{$el.stop().animate(translate,options.time,BEZIER,onEndFn)}}function fade($el1,$el2,$frames,options,fadeStack,chain){var chainedFLAG=typeof chain!=="undefined";if(!chainedFLAG){fadeStack.push(arguments);Array.prototype.push.call(arguments,fadeStack.length);if(fadeStack.length>1)return}$el1=$el1||$($el1);$el2=$el2||$($el2);var _$el1=$el1[0],_$el2=$el2[0],crossfadeFLAG=options.method==="crossfade",onEndFn=function(){if(!onEndFn.done){onEndFn.done=true;var args=(chainedFLAG||fadeStack.shift())&&fadeStack.shift();args&&fade.apply(this,args);(options.onEnd||noop)(!!args)}},time=options.time/(chain||1);$frames.removeClass(fadeRearClass+" "+fadeFrontClass);$el1.stop().addClass(fadeRearClass);$el2.stop().addClass(fadeFrontClass);crossfadeFLAG&&_$el2&&$el1.fadeTo(0,0);$el1.fadeTo(crossfadeFLAG?time:0,1,crossfadeFLAG&&onEndFn);$el2.fadeTo(time,0,onEndFn);_$el1&&crossfadeFLAG||_$el2||onEndFn()}var lastEvent,moveEventType,preventEvent,preventEventTimeout,dragDomEl;function extendEvent(e){var touch=(e.touches||[])[0]||e;e._x=touch.pageX||touch.originalEvent.pageX;e._y=touch.clientY||touch.originalEvent.clientY;e._now=$.now()}function touch($el,options){var el=$el[0],tail={},touchEnabledFLAG,startEvent,$target,controlTouch,touchFLAG,targetIsSelectFLAG,targetIsLinkFlag,tolerance,moved;function onStart(e){$target=$(e.target);tail.checked=targetIsSelectFLAG=targetIsLinkFlag=moved=false;if(touchEnabledFLAG||tail.flow||e.touches&&e.touches.length>1||e.which>1||lastEvent&&lastEvent.type!==e.type&&preventEvent||(targetIsSelectFLAG=options.select&&$target.is(options.select,el)))return targetIsSelectFLAG;touchFLAG=e.type==="touchstart";targetIsLinkFlag=$target.is("a, a *",el);controlTouch=tail.control;tolerance=tail.noMove||tail.noSwipe||controlTouch?16:!tail.snap?4:0;extendEvent(e);startEvent=lastEvent=e;moveEventType=e.type.replace(/down|start/,"move").replace(/Down/,"Move");(options.onStart||noop).call(el,e,{control:controlTouch,$target:$target});touchEnabledFLAG=tail.flow=true;if(!touchFLAG||tail.go)stopEvent(e)}function onMove(e){if(e.touches&&e.touches.length>1||MS_POINTER&&!e.isPrimary||moveEventType!==e.type||!touchEnabledFLAG){touchEnabledFLAG&&onEnd();(options.onTouchEnd||noop)();return}extendEvent(e);var xDiff=Math.abs(e._x-startEvent._x),yDiff=Math.abs(e._y-startEvent._y),xyDiff=xDiff-yDiff,xWin=(tail.go||tail.x||xyDiff>=0)&&!tail.noSwipe,yWin=xyDiff<0;if(touchFLAG&&!tail.checked){if(touchEnabledFLAG=xWin){stopEvent(e)}}else{stopEvent(e);if(movedEnough(xDiff,yDiff)){(options.onMove||noop).call(el,e,{touch:touchFLAG})}}if(!moved&&movedEnough(xDiff,yDiff)&&Math.sqrt(Math.pow(xDiff,2)+Math.pow(yDiff,2))>tolerance){moved=true}tail.checked=tail.checked||xWin||yWin}function movedEnough(xDiff,yDiff){return xDiff>yDiff&&xDiff>1.5}function onEnd(e){(options.onTouchEnd||noop)();var _touchEnabledFLAG=touchEnabledFLAG;tail.control=touchEnabledFLAG=false;if(_touchEnabledFLAG){tail.flow=false}if(!_touchEnabledFLAG||targetIsLinkFlag&&!tail.checked)return;e&&stopEvent(e);preventEvent=true;clearTimeout(preventEventTimeout);preventEventTimeout=setTimeout(function(){preventEvent=false},1e3);(options.onEnd||noop).call(el,{moved:moved,$target:$target,control:controlTouch,touch:touchFLAG,startEvent:startEvent,aborted:!e||e.type==="MSPointerCancel"})}function onOtherStart(){if(tail.flow)return;tail.flow=true}function onOtherEnd(){if(!tail.flow)return;tail.flow=false}if(MS_POINTER){addEvent(el,"MSPointerDown",onStart);addEvent(document,"MSPointerMove",onMove);addEvent(document,"MSPointerCancel",onEnd);addEvent(document,"MSPointerUp",onEnd)}else{addEvent(el,"touchstart",onStart);addEvent(el,"touchmove",onMove);addEvent(el,"touchend",onEnd);addEvent(document,"touchstart",onOtherStart);addEvent(document,"touchend",onOtherEnd);addEvent(document,"touchcancel",onOtherEnd);$WINDOW.on("scroll",onOtherEnd);$el.on("mousedown pointerdown",onStart);$DOCUMENT.on("mousemove pointermove",onMove).on("mouseup pointerup",onEnd)}if(Modernizr.touch){dragDomEl="a"}else{dragDomEl="div"}$el.on("click",dragDomEl,function(e){tail.checked&&stopEvent(e)});return tail}function moveOnTouch($el,options){var el=$el[0],elData=$el.data(),tail={},startCoo,coo,startElPos,moveElPos,edge,moveTrack,startTime,endTime,min,max,snap,dir,slowFLAG,controlFLAG,moved,tracked;function startTracking(e,noStop){tracked=true;startCoo=coo=dir==="vertical"?e._y:e._x;startTime=e._now;moveTrack=[[startTime,startCoo]];startElPos=moveElPos=tail.noMove||noStop?0:stop($el,(options.getPos||noop)());(options.onStart||noop).call(el,e)}function onStart(e,result){min=tail.min;max=tail.max;snap=tail.snap,dir=tail.direction||"horizontal",$el.navdir=dir;slowFLAG=e.altKey;tracked=moved=false;controlFLAG=result.control;if(!controlFLAG&&!elData.sliding){startTracking(e)}}function onMove(e,result){if(!tail.noSwipe){if(!tracked){startTracking(e)}coo=dir==="vertical"?e._y:e._x;moveTrack.push([e._now,coo]);moveElPos=startElPos-(startCoo-coo);edge=findShadowEdge(moveElPos,min,max,dir);if(moveElPos<=min){moveElPos=edgeResistance(moveElPos,min)}else if(moveElPos>=max){moveElPos=edgeResistance(moveElPos,max)}if(!tail.noMove){$el.css(getTranslate(moveElPos,dir));if(!moved){moved=true;result.touch||MS_POINTER||$el.addClass(grabbingClass)}(options.onMove||noop).call(el,e,{pos:moveElPos,edge:edge})}}}function onEnd(result){if(tail.noSwipe&&result.moved)return;if(!tracked){startTracking(result.startEvent,true)}result.touch||MS_POINTER||$el.removeClass(grabbingClass);endTime=$.now();var _backTimeIdeal=endTime-TOUCH_TIMEOUT,_backTime,_timeDiff,_timeDiffLast,backTime=null,backCoo,virtualPos,limitPos,newPos,overPos,time=TRANSITION_DURATION,speed,friction=options.friction;for(var _i=moveTrack.length-1;_i>=0;_i--){_backTime=moveTrack[_i][0];_timeDiff=Math.abs(_backTime-_backTimeIdeal);if(backTime===null||_timeDiff<_timeDiffLast){backTime=_backTime;backCoo=moveTrack[_i][1]}else if(backTime===_backTimeIdeal||_timeDiff>_timeDiffLast){break}_timeDiffLast=_timeDiff}newPos=minMaxLimit(moveElPos,min,max);var cooDiff=backCoo-coo,forwardFLAG=cooDiff>=0,timeDiff=endTime-backTime,longTouchFLAG=timeDiff>TOUCH_TIMEOUT,swipeFLAG=!longTouchFLAG&&moveElPos!==startElPos&&newPos===moveElPos;if(snap){newPos=minMaxLimit(Math[swipeFLAG?forwardFLAG?"floor":"ceil":"round"](moveElPos/snap)*snap,min,max);min=max=newPos}if(swipeFLAG&&(snap||newPos===moveElPos)){speed=-(cooDiff/timeDiff);time*=minMaxLimit(Math.abs(speed),options.timeLow,options.timeHigh);virtualPos=Math.round(moveElPos+speed*time/friction);if(!snap){newPos=virtualPos}if(!forwardFLAG&&virtualPos>max||forwardFLAG&&virtualPos<min){limitPos=forwardFLAG?min:max;overPos=virtualPos-limitPos;if(!snap){newPos=limitPos}overPos=minMaxLimit(newPos+overPos*.03,limitPos-50,limitPos+50);time=Math.abs((moveElPos-overPos)/(speed/friction))}}time*=slowFLAG?10:1;(options.onEnd||noop).call(el,$.extend(result,{moved:result.moved||longTouchFLAG&&snap,pos:moveElPos,newPos:newPos,overPos:overPos,time:time,dir:dir}))}tail=$.extend(touch(options.$wrap,$.extend({},options,{onStart:onStart,onMove:onMove,onEnd:onEnd})),tail);return tail}function wheel($el,options){var el=$el[0],lockFLAG,lastDirection,lastNow,tail={prevent:{}};addEvent(el,WHEEL,function(e){var yDelta=e.wheelDeltaY||-1*e.deltaY||0,xDelta=e.wheelDeltaX||-1*e.deltaX||0,xWin=Math.abs(xDelta)&&!Math.abs(yDelta),direction=getDirectionSign(xDelta<0),sameDirection=lastDirection===direction,now=$.now(),tooFast=now-lastNow<TOUCH_TIMEOUT;lastDirection=direction;lastNow=now;if(!xWin||!tail.ok||tail.prevent[direction]&&!lockFLAG){return}else{stopEvent(e,true);if(lockFLAG&&sameDirection&&tooFast){return}}if(options.shift){lockFLAG=true;clearTimeout(tail.t);tail.t=setTimeout(function(){lockFLAG=false},SCROLL_LOCK_TIMEOUT)}(options.onEnd||noop)(e,options.shift?direction:xDelta)});return tail}jQuery.Fotorama=function($fotorama,opts){$HTML=$("html");$BODY=$("body");var that=this,stamp=$.now(),stampClass=_fotoramaClass+stamp,fotorama=$fotorama[0],data,dataFrameCount=1,fotoramaData=$fotorama.data(),size,$style=$("<style></style>"),$anchor=$(div(hiddenClass)),$wrap=$fotorama.find(cls(wrapClass)),$stage=$wrap.find(cls(stageClass)),stage=$stage[0],$stageShaft=$fotorama.find(cls(stageShaftClass)),$stageFrame=$(),$arrPrev=$fotorama.find(cls(arrPrevClass)),$arrNext=$fotorama.find(cls(arrNextClass)),$arrs=$fotorama.find(cls(arrClass)),$navWrap=$fotorama.find(cls(navWrapClass)),$nav=$navWrap.find(cls(navClass)),$navShaft=$nav.find(cls(navShaftClass)),$navFrame,$navDotFrame=$(),$navThumbFrame=$(),stageShaftData=$stageShaft.data(),navShaftData=$navShaft.data(),$thumbBorder=$fotorama.find(cls(thumbBorderClass)),$thumbArrLeft=$fotorama.find(cls(thumbArrLeft)),$thumbArrRight=$fotorama.find(cls(thumbArrRight)),$fullscreenIcon=$fotorama.find(cls(fullscreenIconClass)),fullscreenIcon=$fullscreenIcon[0],$videoPlay=$(div(videoPlayClass)),$videoClose=$fotorama.find(cls(videoCloseClass)),videoClose=$videoClose[0],$spinner=$fotorama.find(cls(fotoramaSpinnerClass)),$videoPlaying,activeIndex=false,activeFrame,activeIndexes,repositionIndex,dirtyIndex,lastActiveIndex,prevIndex,nextIndex,nextAutoplayIndex,startIndex,o_loop,o_nav,o_navThumbs,o_navTop,o_allowFullScreen,o_nativeFullScreen,o_fade,o_thumbSide,o_thumbSide2,o_transitionDuration,o_transition,o_shadows,o_rtl,o_keyboard,lastOptions={},measures={},measuresSetFLAG,stageShaftTouchTail={},stageWheelTail={},navShaftTouchTail={},navWheelTail={},scrollTop,scrollLeft,showedFLAG,pausedAutoplayFLAG,stoppedAutoplayFLAG,toDeactivate={},toDetach={},measuresStash,touchedFLAG,hoverFLAG,navFrameKey,stageLeft=0,fadeStack=[];$wrap[STAGE_FRAME_KEY]=$('<div class="'+stageFrameClass+'"></div>');$wrap[NAV_THUMB_FRAME_KEY]=$($.Fotorama.jst.thumb());$wrap[NAV_DOT_FRAME_KEY]=$($.Fotorama.jst.dots());toDeactivate[STAGE_FRAME_KEY]=[];toDeactivate[NAV_THUMB_FRAME_KEY]=[];toDeactivate[NAV_DOT_FRAME_KEY]=[];toDetach[STAGE_FRAME_KEY]={};$wrap.addClass(CSS3?wrapCss3Class:wrapCss2Class);fotoramaData.fotorama=this;function checkForVideo(){$.each(data,function(i,dataFrame){if(!dataFrame.i){dataFrame.i=dataFrameCount++;var video=findVideoId(dataFrame.video,true);if(video){var thumbs={};dataFrame.video=video;if(!dataFrame.img&&!dataFrame.thumb){thumbs=getVideoThumbs(dataFrame,data,that)}else{dataFrame.thumbsReady=true}updateData(data,{img:thumbs.img,thumb:thumbs.thumb},dataFrame.i,that)}}})}function allowKey(key){return o_keyboard[key]}function setStagePosition(){if($stage!==undefined){if(opts.navdir=="vertical"){var padding=opts.thumbwidth+opts.thumbmargin;$stage.css("left",padding);$arrNext.css("right",padding);$fullscreenIcon.css("right",padding);$wrap.css("width",$wrap.css("width")+padding);$stageShaft.css("max-width",$wrap.width()-padding)}else{$stage.css("left","");$arrNext.css("right","");$fullscreenIcon.css("right","");$wrap.css("width",$wrap.css("width")+padding);$stageShaft.css("max-width","")}}}function bindGlobalEvents(FLAG){var keydownCommon="keydown."+_fotoramaClass,localStamp=_fotoramaClass+stamp,keydownLocal="keydown."+localStamp,keyupLocal="keyup."+localStamp,resizeLocal="resize."+localStamp+" "+"orientationchange."+localStamp,showParams;if(FLAG){$DOCUMENT.on(keydownLocal,function(e){var catched,index;if($videoPlaying&&e.keyCode===27){catched=true;unloadVideo($videoPlaying,true,true)}else if(that.fullScreen||opts.keyboard&&!that.index){if(e.keyCode===27){catched=true;that.cancelFullScreen()}else if(e.shiftKey&&e.keyCode===32&&allowKey("space")||e.keyCode===37&&allowKey("left")||e.keyCode===38&&allowKey("up")&&$(":focus").attr("data-gallery-role")){that.longPress.progress();index="<"}else if(e.keyCode===32&&allowKey("space")||e.keyCode===39&&allowKey("right")||e.keyCode===40&&allowKey("down")&&$(":focus").attr("data-gallery-role")){that.longPress.progress();index=">"}else if(e.keyCode===36&&allowKey("home")){that.longPress.progress();index="<<"}else if(e.keyCode===35&&allowKey("end")){that.longPress.progress();index=">>"}}(catched||index)&&stopEvent(e);showParams={index:index,slow:e.altKey,user:true};index&&(that.longPress.inProgress?that.showWhileLongPress(showParams):that.show(showParams))});if(FLAG){$DOCUMENT.on(keyupLocal,function(e){if(that.longPress.inProgress){that.showEndLongPress({user:true})}that.longPress.reset()})}if(!that.index){$DOCUMENT.off(keydownCommon).on(keydownCommon,"textarea, input, select",function(e){!$BODY.hasClass(_fullscreenClass)&&e.stopPropagation()})}$WINDOW.on(resizeLocal,that.resize)}else{$DOCUMENT.off(keydownLocal);$WINDOW.off(resizeLocal)}}function appendElements(FLAG){if(FLAG===appendElements.f)return;if(FLAG){$fotorama.addClass(_fotoramaClass+" "+stampClass).before($anchor).before($style);addInstance(that)}else{$anchor.detach();$style.detach();$fotorama.html(fotoramaData.urtext).removeClass(stampClass);hideInstance(that)}bindGlobalEvents(FLAG);appendElements.f=FLAG}function setData(){data=that.data=data||clone(opts.data)||getDataFromHtml($fotorama);size=that.size=data.length;ready.ok&&opts.shuffle&&shuffle(data);checkForVideo();activeIndex=limitIndex(activeIndex);size&&appendElements(true)}function stageNoMove(){var _noMove=size<2||$videoPlaying;stageShaftTouchTail.noMove=_noMove||o_fade;stageShaftTouchTail.noSwipe=_noMove||!opts.swipe;!o_transition&&$stageShaft.toggleClass(grabClass,!opts.click&&!stageShaftTouchTail.noMove&&!stageShaftTouchTail.noSwipe);MS_POINTER&&$wrap.toggleClass(wrapPanYClass,!stageShaftTouchTail.noSwipe)}function setAutoplayInterval(interval){if(interval===true)interval="";opts.autoplay=Math.max(+interval||AUTOPLAY_INTERVAL,o_transitionDuration*1.5)}function updateThumbArrow(opt){if(opt.navarrows&&opt.nav==="thumbs"){$thumbArrLeft.show();$thumbArrRight.show()}else{$thumbArrLeft.hide();$thumbArrRight.hide()}}function getThumbsInSlide($el,opts){return Math.floor($wrap.width()/(opts.thumbwidth+opts.thumbmargin))}function setOptions(){if(!opts.nav||opts.nav==="dots"){opts.navdir="horizontal"}that.options=opts=optionsToLowerCase(opts);thumbsPerSlide=getThumbsInSlide($wrap,opts);o_fade=opts.transition==="crossfade"||opts.transition==="dissolve";o_loop=opts.loop&&(size>2||o_fade&&(!o_transition||o_transition!=="slide"));o_transitionDuration=+opts.transitionduration||TRANSITION_DURATION;o_rtl=opts.direction==="rtl";o_keyboard=$.extend({},opts.keyboard&&KEYBOARD_OPTIONS,opts.keyboard);updateThumbArrow(opts);var classes={add:[],remove:[]};function addOrRemoveClass(FLAG,value){classes[FLAG?"add":"remove"].push(value)}if(size>1){o_nav=opts.nav;o_navTop=opts.navposition==="top";classes.remove.push(selectClass);$arrs.toggle(opts.arrows)}else{o_nav=false;$arrs.hide()}arrsUpdate();stageWheelUpdate();thumbArrUpdate();if(opts.autoplay)setAutoplayInterval(opts.autoplay);o_thumbSide=numberFromMeasure(opts.thumbwidth)||THUMB_SIZE;o_thumbSide2=numberFromMeasure(opts.thumbheight)||THUMB_SIZE;stageWheelTail.ok=navWheelTail.ok=opts.trackpad&&!SLOW;stageNoMove();extendMeasures(opts,[measures]);o_navThumbs=o_nav==="thumbs";if($navWrap.filter(":hidden")&&!!o_nav){$navWrap.show()}if(o_navThumbs){frameDraw(size,"navThumb");$navFrame=$navThumbFrame;navFrameKey=NAV_THUMB_FRAME_KEY;setStyle($style,$.Fotorama.jst.style({w:o_thumbSide,h:o_thumbSide2,b:opts.thumbborderwidth,m:opts.thumbmargin,s:stamp,q:!COMPAT}));$nav.addClass(navThumbsClass).removeClass(navDotsClass)}else if(o_nav==="dots"){frameDraw(size,"navDot");$navFrame=$navDotFrame;navFrameKey=NAV_DOT_FRAME_KEY;$nav.addClass(navDotsClass).removeClass(navThumbsClass)}else{$navWrap.hide();o_nav=false;$nav.removeClass(navThumbsClass+" "+navDotsClass)}if(o_nav){if(o_navTop){$navWrap.insertBefore($stage)}else{$navWrap.insertAfter($stage)}frameAppend.nav=false;frameAppend($navFrame,$navShaft,"nav")}o_allowFullScreen=opts.allowfullscreen;if(o_allowFullScreen){$fullscreenIcon.prependTo($stage);o_nativeFullScreen=FULLSCREEN&&o_allowFullScreen==="native";stubEvent($fullscreenIcon,"touchend")}else{$fullscreenIcon.detach();o_nativeFullScreen=false}addOrRemoveClass(o_fade,wrapFadeClass);addOrRemoveClass(!o_fade,wrapSlideClass);addOrRemoveClass(!opts.captions,wrapNoCaptionsClass);addOrRemoveClass(o_rtl,wrapRtlClass);addOrRemoveClass(opts.arrows,wrapToggleArrowsClass);o_shadows=opts.shadows&&!SLOW;addOrRemoveClass(!o_shadows,wrapNoShadowsClass);$wrap.addClass(classes.add.join(" ")).removeClass(classes.remove.join(" "));lastOptions=$.extend({},opts);setStagePosition()}function normalizeIndex(index){return index<0?(size+index%size)%size:index>=size?index%size:index}function limitIndex(index){return minMaxLimit(index,0,size-1)}function edgeIndex(index){return o_loop?normalizeIndex(index):limitIndex(index)}function getPrevIndex(index){return index>0||o_loop?index-1:false}function getNextIndex(index){return index<size-1||o_loop?index+1:false}function setStageShaftMinmaxAndSnap(){stageShaftTouchTail.min=o_loop?-Infinity:-getPosByIndex(size-1,measures.w,opts.margin,repositionIndex);stageShaftTouchTail.max=o_loop?Infinity:-getPosByIndex(0,measures.w,opts.margin,repositionIndex);stageShaftTouchTail.snap=measures.w+opts.margin}function setNavShaftMinMax(){var isVerticalDir=opts.navdir==="vertical";var param=isVerticalDir?$navShaft.height():$navShaft.width();var mainParam=isVerticalDir?measures.h:measures.nw;navShaftTouchTail.min=Math.min(0,mainParam-param);navShaftTouchTail.max=0;navShaftTouchTail.direction=opts.navdir;$navShaft.toggleClass(grabClass,!(navShaftTouchTail.noMove=navShaftTouchTail.min===navShaftTouchTail.max))}function eachIndex(indexes,type,fn){if(typeof indexes==="number"){indexes=new Array(indexes);var rangeFLAG=true}return $.each(indexes,function(i,index){if(rangeFLAG)index=i;if(typeof index==="number"){var dataFrame=data[normalizeIndex(index)];if(dataFrame){var key="$"+type+"Frame",$frame=dataFrame[key];fn.call(this,i,index,dataFrame,$frame,key,$frame&&$frame.data())}}})}function setMeasures(width,height,ratio,index){if(!measuresSetFLAG||measuresSetFLAG==="*"&&index===startIndex){width=measureIsValid(opts.width)||measureIsValid(width)||WIDTH;height=measureIsValid(opts.height)||measureIsValid(height)||HEIGHT;that.resize({width:width,ratio:opts.ratio||ratio||width/height},0,index!==startIndex&&"*")}}function loadImg(indexes,type,specialMeasures,again){eachIndex(indexes,type,function(i,index,dataFrame,$frame,key,frameData){if(!$frame)return;var fullFLAG=that.fullScreen&&!frameData.$full&&type==="stage";if(frameData.$img&&!again&&!fullFLAG)return;var img=new Image,$img=$(img),imgData=$img.data();frameData[fullFLAG?"$full":"$img"]=$img;var srcKey=type==="stage"?fullFLAG?"full":"img":"thumb",src=dataFrame[srcKey],dummy=fullFLAG?dataFrame["img"]:dataFrame[type==="stage"?"thumb":"img"];if(type==="navThumb")$frame=frameData.$wrap;function triggerTriggerEvent(event){var _index=normalizeIndex(index);triggerEvent(event,{index:_index,src:src,frame:data[_index]})}function error(){$img.remove();$.Fotorama.cache[src]="error";if((!dataFrame.html||type!=="stage")&&dummy&&dummy!==src){dataFrame[srcKey]=src=dummy;frameData.$full=null;loadImg([index],type,specialMeasures,true)}else{if(src&&!dataFrame.html&&!fullFLAG){$frame.trigger("f:error").removeClass(loadingClass).addClass(errorClass);triggerTriggerEvent("error")}else if(type==="stage"){$frame.trigger("f:load").removeClass(loadingClass+" "+errorClass).addClass(loadedClass);triggerTriggerEvent("load");setMeasures()}frameData.state="error";if(size>1&&data[index]===dataFrame&&!dataFrame.html&&!dataFrame.deleted&&!dataFrame.video&&!fullFLAG){dataFrame.deleted=true;that.splice(index,1)}}}function loaded(){$.Fotorama.measures[src]=imgData.measures=$.Fotorama.measures[src]||{width:img.width,height:img.height,ratio:img.width/img.height};setMeasures(imgData.measures.width,imgData.measures.height,imgData.measures.ratio,index);$img.off("load error").addClass(""+(fullFLAG?imgFullClass:imgClass)).attr("aria-hidden","false").prependTo($frame);if($frame.hasClass(stageFrameClass)&&!$frame.hasClass(videoContainerClass)){$frame.attr("href",$img.attr("src"))}fit($img,($.isFunction(specialMeasures)?specialMeasures():specialMeasures)||measures);$.Fotorama.cache[src]=frameData.state="loaded";setTimeout(function(){$frame.trigger("f:load").removeClass(loadingClass+" "+errorClass).addClass(loadedClass+" "+(fullFLAG?loadedFullClass:loadedImgClass));if(type==="stage"){triggerTriggerEvent("load")}else if(dataFrame.thumbratio===AUTO||!dataFrame.thumbratio&&opts.thumbratio===AUTO){dataFrame.thumbratio=imgData.measures.ratio;reset()}},0)}if(!src){error();return}function waitAndLoad(){var _i=10;waitFor(function(){return!touchedFLAG||!_i--&&!SLOW},function(){loaded()})}if(!$.Fotorama.cache[src]){$.Fotorama.cache[src]="*";$img.on("load",waitAndLoad).on("error",error)}else{(function justWait(){if($.Fotorama.cache[src]==="error"){error()}else if($.Fotorama.cache[src]==="loaded"){setTimeout(waitAndLoad,0)}else{setTimeout(justWait,100)}})()}frameData.state="";img.src=src;if(frameData.data.caption){img.alt=frameData.data.caption||""}if(frameData.data.full){$(img).data("original",frameData.data.full)}if(UTIL.isExpectedCaption(dataFrame,opts.showcaption)){$(img).attr("aria-labelledby",dataFrame.labelledby)}})}function updateFotoramaState(){var $frame=activeFrame[STAGE_FRAME_KEY];if($frame&&!$frame.data().state){$spinner.addClass(spinnerShowClass);$frame.on("f:load f:error",function(){$frame.off("f:load f:error");$spinner.removeClass(spinnerShowClass)})}}function addNavFrameEvents(frame){addEnterUp(frame,onNavFrameClick);addFocus(frame,function(){setTimeout(function(){lockScroll($nav)},0);slideNavShaft({time:o_transitionDuration,guessIndex:$(this).data().eq,minMax:navShaftTouchTail})})}function frameDraw(indexes,type){eachIndex(indexes,type,function(i,index,dataFrame,$frame,key,frameData){if($frame)return;$frame=dataFrame[key]=$wrap[key].clone();frameData=$frame.data();frameData.data=dataFrame;var frame=$frame[0],labelledbyValue="labelledby"+$.now();if(type==="stage"){if(dataFrame.html){$('<div class="'+htmlClass+'"></div>').append(dataFrame._html?$(dataFrame.html).removeAttr("id").html(dataFrame._html):dataFrame.html).appendTo($frame)}if(dataFrame.id){labelledbyValue=dataFrame.id||labelledbyValue}dataFrame.labelledby=labelledbyValue;if(UTIL.isExpectedCaption(dataFrame,opts.showcaption)){$($.Fotorama.jst.frameCaption({caption:dataFrame.caption,labelledby:labelledbyValue})).appendTo($frame)}dataFrame.video&&$frame.addClass(stageFrameVideoClass).append($videoPlay.clone());addFocus(frame,function(){setTimeout(function(){lockScroll($stage)},0);clickToShow({index:frameData.eq,user:true})});$stageFrame=$stageFrame.add($frame)}else if(type==="navDot"){addNavFrameEvents(frame);$navDotFrame=$navDotFrame.add($frame)}else if(type==="navThumb"){addNavFrameEvents(frame);frameData.$wrap=$frame.children(":first");$navThumbFrame=$navThumbFrame.add($frame);if(dataFrame.video){frameData.$wrap.append($videoPlay.clone())}}})}function callFit($img,measuresToFit){return $img&&$img.length&&fit($img,measuresToFit)}function stageFramePosition(indexes){eachIndex(indexes,"stage",function(i,index,dataFrame,$frame,key,frameData){if(!$frame)return;var normalizedIndex=normalizeIndex(index);frameData.eq=normalizedIndex;toDetach[STAGE_FRAME_KEY][normalizedIndex]=$frame.css($.extend({left:o_fade?0:getPosByIndex(index,measures.w,opts.margin,repositionIndex)},o_fade&&getDuration(0)));if(isDetached($frame[0])){$frame.appendTo($stageShaft);unloadVideo(dataFrame.$video)}callFit(frameData.$img,measures);callFit(frameData.$full,measures);if($frame.hasClass(stageFrameClass)&&!($frame.attr("aria-hidden")==="false"&&$frame.hasClass(activeClass))){$frame.attr("aria-hidden","true")}})}function thumbsDraw(pos,loadFLAG){var leftLimit,rightLimit,exceedLimit;if(o_nav!=="thumbs"||isNaN(pos))return;leftLimit=-pos;rightLimit=-pos+measures.nw;if(opts.navdir==="vertical"){pos=pos-opts.thumbheight;rightLimit=-pos+measures.h}$navThumbFrame.each(function(){var $this=$(this),thisData=$this.data(),eq=thisData.eq,getSpecialMeasures=function(){return{h:o_thumbSide2,w:thisData.w}},specialMeasures=getSpecialMeasures(),exceedLimit=opts.navdir==="vertical"?thisData.t>rightLimit:thisData.l>rightLimit;specialMeasures.w=thisData.w;if(thisData.l+thisData.w<leftLimit||exceedLimit||callFit(thisData.$img,specialMeasures))return;loadFLAG&&loadImg([eq],"navThumb",getSpecialMeasures)})}function frameAppend($frames,$shaft,type){if(!frameAppend[type]){var thumbsFLAG=type==="nav"&&o_navThumbs,left=0,top=0;$shaft.append($frames.filter(function(){var actual,$this=$(this),frameData=$this.data();for(var _i=0,_l=data.length;_i<_l;_i++){if(frameData.data===data[_i]){actual=true;frameData.eq=_i;break}}return actual||$this.remove()&&false}).sort(function(a,b){return $(a).data().eq-$(b).data().eq}).each(function(){var $this=$(this),frameData=$this.data();UTIL.setThumbAttr($this,frameData.data.caption,"aria-label")}).each(function(){if(!thumbsFLAG)return;var $this=$(this),frameData=$this.data(),thumbwidth=Math.round(o_thumbSide2*frameData.data.thumbratio)||o_thumbSide,thumbheight=Math.round(o_thumbSide/frameData.data.thumbratio)||o_thumbSide2;frameData.t=top;frameData.h=thumbheight;frameData.l=left;frameData.w=thumbwidth;$this.css({width:thumbwidth});top+=thumbheight+opts.thumbmargin;left+=thumbwidth+opts.thumbmargin}));frameAppend[type]=true}}function getDirection(x){return x-stageLeft>measures.w/3}function disableDirrection(i){return!o_loop&&(!(activeIndex+i)||!(activeIndex-size+i))&&!$videoPlaying}function arrsUpdate(){var disablePrev=disableDirrection(0),disableNext=disableDirrection(1);$arrPrev.toggleClass(arrDisabledClass,disablePrev).attr(disableAttr(disablePrev,false));$arrNext.toggleClass(arrDisabledClass,disableNext).attr(disableAttr(disableNext,false))}function thumbArrUpdate(){var isLeftDisable=false,isRightDisable=false;if(opts.navtype==="thumbs"&&!opts.loop){activeIndex==0?isLeftDisable=true:isLeftDisable=false;activeIndex==opts.data.length-1?isRightDisable=true:isRightDisable=false}if(opts.navtype==="slides"){var pos=readPosition($navShaft,opts.navdir);pos>=navShaftTouchTail.max?isLeftDisable=true:isLeftDisable=false;pos<=navShaftTouchTail.min?isRightDisable=true:isRightDisable=false}$thumbArrLeft.toggleClass(arrDisabledClass,isLeftDisable).attr(disableAttr(isLeftDisable,true));$thumbArrRight.toggleClass(arrDisabledClass,isRightDisable).attr(disableAttr(isRightDisable,true))}function stageWheelUpdate(){if(stageWheelTail.ok){stageWheelTail.prevent={"<":disableDirrection(0),">":disableDirrection(1)}}}function getNavFrameBounds($navFrame){var navFrameData=$navFrame.data(),left,top,width,height;if(o_navThumbs){left=navFrameData.l;top=navFrameData.t;width=navFrameData.w;height=navFrameData.h}else{left=$navFrame.position().left;width=$navFrame.width()}var horizontalBounds={c:left+width/2,min:-left+opts.thumbmargin*10,max:-left+measures.w-width-opts.thumbmargin*10};var verticalBounds={c:top+height/2,min:-top+opts.thumbmargin*10,max:-top+measures.h-height-opts.thumbmargin*10};return opts.navdir==="vertical"?verticalBounds:horizontalBounds}function slideThumbBorder(time){var navFrameData=activeFrame[navFrameKey].data();slide($thumbBorder,{time:time*1.2,pos:opts.navdir==="vertical"?navFrameData.t:navFrameData.l,width:navFrameData.w,height:navFrameData.h,direction:opts.navdir})}function slideNavShaft(options){var $guessNavFrame=data[options.guessIndex][navFrameKey],typeOfAnimation=opts.navtype;var overflowFLAG,time,minMax,boundTop,boundLeft,l,pos,x;if($guessNavFrame){if(typeOfAnimation==="thumbs"){overflowFLAG=navShaftTouchTail.min!==navShaftTouchTail.max;minMax=options.minMax||overflowFLAG&&getNavFrameBounds(activeFrame[navFrameKey]);boundTop=overflowFLAG&&(options.keep&&slideNavShaft.t?slideNavShaft.l:minMaxLimit((options.coo||measures.nw/2)-getNavFrameBounds($guessNavFrame).c,minMax.min,minMax.max));boundLeft=overflowFLAG&&(options.keep&&slideNavShaft.l?slideNavShaft.l:minMaxLimit((options.coo||measures.nw/2)-getNavFrameBounds($guessNavFrame).c,minMax.min,minMax.max));l=opts.navdir==="vertical"?boundTop:boundLeft;pos=overflowFLAG&&minMaxLimit(l,navShaftTouchTail.min,navShaftTouchTail.max)||0;time=options.time*1.1;slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:function(){thumbsDraw(pos,true);thumbArrUpdate()}});setShadow($nav,findShadowEdge(pos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir));slideNavShaft.l=l}else{x=readPosition($navShaft,opts.navdir);time=options.time*1.11;pos=validateSlidePos(opts,navShaftTouchTail,options.guessIndex,x,$guessNavFrame,$navWrap,opts.navdir);slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:function(){thumbsDraw(pos,true);thumbArrUpdate()}});setShadow($nav,findShadowEdge(pos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir))}}}function navUpdate(){deactivateFrames(navFrameKey);toDeactivate[navFrameKey].push(activeFrame[navFrameKey].addClass(activeClass).attr("data-active",true))}function deactivateFrames(key){var _toDeactivate=toDeactivate[key];while(_toDeactivate.length){_toDeactivate.shift().removeClass(activeClass).attr("data-active",false)}}function detachFrames(key){var _toDetach=toDetach[key];$.each(activeIndexes,function(i,index){delete _toDetach[normalizeIndex(index)]});$.each(_toDetach,function(index,$frame){delete _toDetach[index];$frame.detach()})}function stageShaftReposition(skipOnEnd){repositionIndex=dirtyIndex=activeIndex;var $frame=activeFrame[STAGE_FRAME_KEY];if($frame){deactivateFrames(STAGE_FRAME_KEY);toDeactivate[STAGE_FRAME_KEY].push($frame.addClass(activeClass).attr("data-active",true));if($frame.hasClass(stageFrameClass)){$frame.attr("aria-hidden","false")}skipOnEnd||that.showStage.onEnd(true);stop($stageShaft,0,true);detachFrames(STAGE_FRAME_KEY);stageFramePosition(activeIndexes);setStageShaftMinmaxAndSnap();setNavShaftMinMax();addEnterUp($stageShaft[0],function(){if(!$fotorama.hasClass(fullscreenClass)){that.requestFullScreen();$fullscreenIcon.focus()}})}}function extendMeasures(options,measuresArray){if(!options)return;$.each(measuresArray,function(i,measures){if(!measures)return;$.extend(measures,{width:options.width||measures.width,height:options.height,minwidth:options.minwidth,maxwidth:options.maxwidth,minheight:options.minheight,maxheight:options.maxheight,ratio:getRatio(options.ratio)})})}function triggerEvent(event,extra){$fotorama.trigger(_fotoramaClass+":"+event,[that,extra])}function onTouchStart(){clearTimeout(onTouchEnd.t);touchedFLAG=1;if(opts.stopautoplayontouch){that.stopAutoplay()}else{pausedAutoplayFLAG=true}}function onTouchEnd(){if(!touchedFLAG)return;if(!opts.stopautoplayontouch){releaseAutoplay();changeAutoplay()}onTouchEnd.t=setTimeout(function(){touchedFLAG=0},TRANSITION_DURATION+TOUCH_TIMEOUT)}function releaseAutoplay(){pausedAutoplayFLAG=!!($videoPlaying||stoppedAutoplayFLAG)}function changeAutoplay(){clearTimeout(changeAutoplay.t);waitFor.stop(changeAutoplay.w);if(!opts.autoplay||pausedAutoplayFLAG){if(that.autoplay){that.autoplay=false;triggerEvent("stopautoplay")}return}if(!that.autoplay){that.autoplay=true;triggerEvent("startautoplay")}var _activeIndex=activeIndex;var frameData=activeFrame[STAGE_FRAME_KEY].data();changeAutoplay.w=waitFor(function(){return frameData.state||_activeIndex!==activeIndex},function(){changeAutoplay.t=setTimeout(function(){if(pausedAutoplayFLAG||_activeIndex!==activeIndex)return;var _nextAutoplayIndex=nextAutoplayIndex,nextFrameData=data[_nextAutoplayIndex][STAGE_FRAME_KEY].data();changeAutoplay.w=waitFor(function(){return nextFrameData.state||_nextAutoplayIndex!==nextAutoplayIndex},function(){if(pausedAutoplayFLAG||_nextAutoplayIndex!==nextAutoplayIndex)return;that.show(o_loop?getDirectionSign(!o_rtl):nextAutoplayIndex)})},opts.autoplay)})}that.startAutoplay=function(interval){if(that.autoplay)return this;pausedAutoplayFLAG=stoppedAutoplayFLAG=false;setAutoplayInterval(interval||opts.autoplay);changeAutoplay();return this};that.stopAutoplay=function(){if(that.autoplay){pausedAutoplayFLAG=stoppedAutoplayFLAG=true;changeAutoplay()}return this};that.showSlide=function(slideDir){var currentPosition=readPosition($navShaft,opts.navdir),pos,time=500*1.1,size=opts.navdir==="horizontal"?opts.thumbwidth:opts.thumbheight,onEnd=function(){thumbArrUpdate()};if(slideDir==="next"){pos=currentPosition-(size+opts.margin)*thumbsPerSlide}if(slideDir==="prev"){pos=currentPosition+(size+opts.margin)*thumbsPerSlide}pos=validateRestrictions(pos,navShaftTouchTail);thumbsDraw(pos,true);slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:onEnd})};that.showWhileLongPress=function(options){if(that.longPress.singlePressInProgress){return}var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options)/50;var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showNav(silent,options,time);return this};that.showEndLongPress=function(options){if(that.longPress.singlePressInProgress){return}var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options)/50;var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showStage(silent,options,time);showedFLAG=typeof lastActiveIndex!=="undefined"&&lastActiveIndex!==activeIndex;lastActiveIndex=activeIndex;return this};function calcActiveIndex(options){var index;if(typeof options!=="object"){index=options;options={}}else{index=options.index}index=index===">"?dirtyIndex+1:index==="<"?dirtyIndex-1:index==="<<"?0:index===">>"?size-1:index;index=isNaN(index)?undefined:index;index=typeof index==="undefined"?activeIndex||0:index;return index}function calcGlobalIndexes(index){that.activeIndex=activeIndex=edgeIndex(index);prevIndex=getPrevIndex(activeIndex);nextIndex=getNextIndex(activeIndex);nextAutoplayIndex=normalizeIndex(activeIndex+(o_rtl?-1:1));activeIndexes=[activeIndex,prevIndex,nextIndex];dirtyIndex=o_loop?index:activeIndex}function calcTime(options){var diffIndex=Math.abs(lastActiveIndex-dirtyIndex),time=getNumber(options.time,function(){return Math.min(o_transitionDuration*(1+(diffIndex-1)/12),o_transitionDuration*2)});if(options.slow){time*=10}return time}that.showStage=function(silent,options,time){unloadVideo($videoPlaying,activeFrame.i!==data[normalizeIndex(repositionIndex)].i);frameDraw(activeIndexes,"stage");stageFramePosition(SLOW?[dirtyIndex]:[dirtyIndex,getPrevIndex(dirtyIndex),getNextIndex(dirtyIndex)]);updateTouchTails("go",true);silent||triggerEvent("show",{user:options.user,time:time});pausedAutoplayFLAG=true;var overPos=options.overPos;var onEnd=that.showStage.onEnd=function(skipReposition){if(onEnd.ok)return;onEnd.ok=true;skipReposition||stageShaftReposition(true);if(!silent){triggerEvent("showend",{user:options.user})}if(!skipReposition&&o_transition&&o_transition!==opts.transition){that.setOptions({transition:o_transition});o_transition=false;return}updateFotoramaState();loadImg(activeIndexes,"stage");updateTouchTails("go",false);stageWheelUpdate();stageCursor();releaseAutoplay();changeAutoplay();if(that.fullScreen){activeFrame[STAGE_FRAME_KEY].find("."+imgFullClass).attr("aria-hidden",false);activeFrame[STAGE_FRAME_KEY].find("."+imgClass).attr("aria-hidden",true)}else{activeFrame[STAGE_FRAME_KEY].find("."+imgFullClass).attr("aria-hidden",true);activeFrame[STAGE_FRAME_KEY].find("."+imgClass).attr("aria-hidden",false)}};if(!o_fade){slide($stageShaft,{pos:-getPosByIndex(dirtyIndex,measures.w,opts.margin,repositionIndex),overPos:overPos,time:time,onEnd:onEnd})}else{var $activeFrame=activeFrame[STAGE_FRAME_KEY],$prevActiveFrame=data[lastActiveIndex]&&activeIndex!==lastActiveIndex?data[lastActiveIndex][STAGE_FRAME_KEY]:null;fade($activeFrame,$prevActiveFrame,$stageFrame,{time:time,method:opts.transition,onEnd:onEnd},fadeStack)}arrsUpdate()};that.showNav=function(silent,options,time){thumbArrUpdate();if(o_nav){navUpdate();var guessIndex=limitIndex(activeIndex+minMaxLimit(dirtyIndex-lastActiveIndex,-1,1));slideNavShaft({time:time,coo:guessIndex!==activeIndex&&options.coo,guessIndex:typeof options.coo!=="undefined"?guessIndex:activeIndex,keep:silent});if(o_navThumbs)slideThumbBorder(time)}};that.show=function(options){that.longPress.singlePressInProgress=true;var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options);var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showStage(silent,options,time);that.showNav(silent,options,time);showedFLAG=typeof lastActiveIndex!=="undefined"&&lastActiveIndex!==activeIndex;lastActiveIndex=activeIndex;that.longPress.singlePressInProgress=false;return this};that.requestFullScreen=function(){if(o_allowFullScreen&&!that.fullScreen){var isVideo=$((that.activeFrame||{}).$stageFrame||{}).hasClass("fotorama-video-container");if(isVideo){return}scrollTop=$WINDOW.scrollTop();scrollLeft=$WINDOW.scrollLeft();lockScroll($WINDOW);updateTouchTails("x",true);measuresStash=$.extend({},measures);$fotorama.addClass(fullscreenClass).appendTo($BODY.addClass(_fullscreenClass));$HTML.addClass(_fullscreenClass);unloadVideo($videoPlaying,true,true);that.fullScreen=true;if(o_nativeFullScreen){fullScreenApi.request(fotorama)}that.resize();loadImg(activeIndexes,"stage");updateFotoramaState();triggerEvent("fullscreenenter");if(!("ontouchstart"in window)){$fullscreenIcon.focus()}}return this};function cancelFullScreen(){if(that.fullScreen){that.fullScreen=false;if(FULLSCREEN){fullScreenApi.cancel(fotorama)}$BODY.removeClass(_fullscreenClass);$HTML.removeClass(_fullscreenClass);$fotorama.removeClass(fullscreenClass).insertAfter($anchor);measures=$.extend({},measuresStash);unloadVideo($videoPlaying,true,true);updateTouchTails("x",false);that.resize();loadImg(activeIndexes,"stage");lockScroll($WINDOW,scrollLeft,scrollTop);triggerEvent("fullscreenexit")}}that.cancelFullScreen=function(){if(o_nativeFullScreen&&fullScreenApi.is()){fullScreenApi.cancel(document)}else{cancelFullScreen()}return this};that.toggleFullScreen=function(){return that[(that.fullScreen?"cancel":"request")+"FullScreen"]()};that.resize=function(options){if(!data)return this;var time=arguments[1]||0,setFLAG=arguments[2];thumbsPerSlide=getThumbsInSlide($wrap,opts);extendMeasures(!that.fullScreen?optionsToLowerCase(options):{width:$(window).width(),maxwidth:null,minwidth:null,height:$(window).height(),maxheight:null,minheight:null},[measures,setFLAG||that.fullScreen||opts]);var width=measures.width,height=measures.height,ratio=measures.ratio,windowHeight=$WINDOW.height()-(o_nav?$nav.height():0);if(measureIsValid(width)){$wrap.css({width:""});$wrap.css({height:""});$stage.css({width:""});$stage.css({height:""});$stageShaft.css({width:""});$stageShaft.css({height:""});$nav.css({width:""});$nav.css({height:""});$wrap.css({minWidth:measures.minwidth||0,maxWidth:measures.maxwidth||MAX_WIDTH});if(o_nav==="dots"){$navWrap.hide()}width=measures.W=measures.w=$wrap.width();measures.nw=o_nav&&numberFromWhatever(opts.navwidth,width)||width;$stageShaft.css({width:measures.w,marginLeft:(measures.W-measures.w)/2});height=numberFromWhatever(height,windowHeight);height=height||ratio&&width/ratio;if(height){width=Math.round(width);height=measures.h=Math.round(minMaxLimit(height,numberFromWhatever(measures.minheight,windowHeight),numberFromWhatever(measures.maxheight,windowHeight)));$stage.css({width:width,height:height});if(opts.navdir==="vertical"&&!that.fullscreen){$nav.width(opts.thumbwidth+opts.thumbmargin*2)}if(opts.navdir==="horizontal"&&!that.fullscreen){$nav.height(opts.thumbheight+opts.thumbmargin*2)}if(o_nav==="dots"){$nav.width(width).height("auto");$navWrap.show()}if(opts.navdir==="vertical"&&that.fullScreen){$stage.css("height",$WINDOW.height())}if(opts.navdir==="horizontal"&&that.fullScreen){$stage.css("height",$WINDOW.height()-$nav.height())}if(o_nav){switch(opts.navdir){case"vertical":$navWrap.removeClass(navShafthorizontalClass);$navWrap.removeClass(navShaftListClass);$navWrap.addClass(navShaftVerticalClass);$nav.stop().animate({height:measures.h,width:opts.thumbwidth},time);break;case"list":$navWrap.removeClass(navShaftVerticalClass);$navWrap.removeClass(navShafthorizontalClass);$navWrap.addClass(navShaftListClass);break;default:$navWrap.removeClass(navShaftVerticalClass);$navWrap.removeClass(navShaftListClass);$navWrap.addClass(navShafthorizontalClass);$nav.stop().animate({width:measures.nw},time);break}stageShaftReposition();slideNavShaft({guessIndex:activeIndex,time:time,keep:true});if(o_navThumbs&&frameAppend.nav)slideThumbBorder(time)}measuresSetFLAG=setFLAG||true;ready.ok=true;ready()}}stageLeft=$stage.offset().left;setStagePosition();return this};that.setOptions=function(options){$.extend(opts,options);reset();return this};that.shuffle=function(){data&&shuffle(data)&&reset();return this};function setShadow($el,edge){if(o_shadows){$el.removeClass(shadowsLeftClass+" "+shadowsRightClass);$el.removeClass(shadowsTopClass+" "+shadowsBottomClass);edge&&!$videoPlaying&&$el.addClass(edge.replace(/^|\s/g," "+shadowsClass+"--"))}}that.longPress={threshold:1,count:0,thumbSlideTime:20,progress:function(){if(!this.inProgress){this.count++;this.inProgress=this.count>this.threshold}},end:function(){if(this.inProgress){this.isEnded=true}},reset:function(){this.count=0;this.inProgress=false;this.isEnded=false}};that.destroy=function(){that.cancelFullScreen();that.stopAutoplay();data=that.data=null;appendElements();activeIndexes=[];detachFrames(STAGE_FRAME_KEY);reset.ok=false;return this};that.playVideo=function(){var dataFrame=activeFrame,video=dataFrame.video,_activeIndex=activeIndex;if(typeof video==="object"&&dataFrame.videoReady){o_nativeFullScreen&&that.fullScreen&&that.cancelFullScreen();waitFor(function(){return!fullScreenApi.is()||_activeIndex!==activeIndex},function(){if(_activeIndex===activeIndex){dataFrame.$video=dataFrame.$video||$(div(videoClass)).append(createVideoFrame(video));dataFrame.$video.appendTo(dataFrame[STAGE_FRAME_KEY]);$wrap.addClass(wrapVideoClass);$videoPlaying=dataFrame.$video;stageNoMove();$arrs.blur();$fullscreenIcon.blur();triggerEvent("loadvideo")}})}return this};that.stopVideo=function(){unloadVideo($videoPlaying,true,true);return this};that.spliceByIndex=function(index,newImgObj){newImgObj.i=index+1;newImgObj.img&&$.ajax({url:newImgObj.img,type:"HEAD",success:function(){data.splice(index,1,newImgObj);reset()}})};function unloadVideo($video,unloadActiveFLAG,releaseAutoplayFLAG){if(unloadActiveFLAG){$wrap.removeClass(wrapVideoClass);$videoPlaying=false;stageNoMove()}if($video&&$video!==$videoPlaying){$video.remove();triggerEvent("unloadvideo")}if(releaseAutoplayFLAG){releaseAutoplay();changeAutoplay()}}function toggleControlsClass(FLAG){$wrap.toggleClass(wrapNoControlsClass,FLAG)}function stageCursor(e){if(stageShaftTouchTail.flow)return;var x=e?e.pageX:stageCursor.x,pointerFLAG=x&&!disableDirrection(getDirection(x))&&opts.click;if(stageCursor.p!==pointerFLAG&&$stage.toggleClass(pointerClass,pointerFLAG)){stageCursor.p=pointerFLAG;stageCursor.x=x}}$stage.on("mousemove",stageCursor);function clickToShow(showOptions){clearTimeout(clickToShow.t);if(opts.clicktransition&&opts.clicktransition!==opts.transition){setTimeout(function(){var _o_transition=opts.transition;that.setOptions({transition:opts.clicktransition});o_transition=_o_transition;clickToShow.t=setTimeout(function(){that.show(showOptions)},10)},0)}else{that.show(showOptions)}}function onStageTap(e,toggleControlsFLAG){var target=e.target,$target=$(target);if($target.hasClass(videoPlayClass)){that.playVideo()}else if(target===fullscreenIcon){that.toggleFullScreen()}else if($videoPlaying){target===videoClose&&unloadVideo($videoPlaying,true,true)}else if(!$fotorama.hasClass(fullscreenClass)){that.requestFullScreen()}}function updateTouchTails(key,value){stageShaftTouchTail[key]=navShaftTouchTail[key]=value}stageShaftTouchTail=moveOnTouch($stageShaft,{onStart:onTouchStart,onMove:function(e,result){setShadow($stage,result.edge)},onTouchEnd:onTouchEnd,onEnd:function(result){var toggleControlsFLAG;setShadow($stage);toggleControlsFLAG=(MS_POINTER&&!hoverFLAG||result.touch)&&opts.arrows;if((result.moved||toggleControlsFLAG&&result.pos!==result.newPos&&!result.control)&&result.$target[0]!==$fullscreenIcon[0]){var index=getIndexByPos(result.newPos,measures.w,opts.margin,repositionIndex);that.show({index:index,time:o_fade?o_transitionDuration:result.time,overPos:result.overPos,user:true})}else if(!result.aborted&&!result.control){onStageTap(result.startEvent,toggleControlsFLAG)}},timeLow:1,timeHigh:1,friction:2,select:"."+selectClass+", ."+selectClass+" *",$wrap:$stage,direction:"horizontal"});navShaftTouchTail=moveOnTouch($navShaft,{onStart:onTouchStart,onMove:function(e,result){setShadow($nav,result.edge)},onTouchEnd:onTouchEnd,onEnd:function(result){function onEnd(){slideNavShaft.l=result.newPos;releaseAutoplay();changeAutoplay();thumbsDraw(result.newPos,true);thumbArrUpdate()}if(!result.moved){var target=result.$target.closest("."+navFrameClass,$navShaft)[0];target&&onNavFrameClick.call(target,result.startEvent)}else if(result.pos!==result.newPos){pausedAutoplayFLAG=true;slide($navShaft,{time:result.time,pos:result.newPos,overPos:result.overPos,direction:opts.navdir,onEnd:onEnd});thumbsDraw(result.newPos);o_shadows&&setShadow($nav,findShadowEdge(result.newPos,navShaftTouchTail.min,navShaftTouchTail.max,result.dir))}else{onEnd()}},timeLow:.5,timeHigh:2,friction:5,$wrap:$nav,direction:opts.navdir});stageWheelTail=wheel($stage,{shift:true,onEnd:function(e,direction){onTouchStart();onTouchEnd();that.show({index:direction,slow:e.altKey})}});navWheelTail=wheel($nav,{onEnd:function(e,direction){onTouchStart();onTouchEnd();var newPos=stop($navShaft)+direction*.25;$navShaft.css(getTranslate(minMaxLimit(newPos,navShaftTouchTail.min,navShaftTouchTail.max),opts.navdir));o_shadows&&setShadow($nav,findShadowEdge(newPos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir));navWheelTail.prevent={"<":newPos>=navShaftTouchTail.max,">":newPos<=navShaftTouchTail.min};clearTimeout(navWheelTail.t);navWheelTail.t=setTimeout(function(){slideNavShaft.l=newPos;thumbsDraw(newPos,true)},TOUCH_TIMEOUT);thumbsDraw(newPos)}});$wrap.hover(function(){setTimeout(function(){if(touchedFLAG)return;toggleControlsClass(!(hoverFLAG=true))},0)},function(){if(!hoverFLAG)return;toggleControlsClass(!(hoverFLAG=false))});function onNavFrameClick(e){var index=$(this).data().eq;if(opts.navtype==="thumbs"){clickToShow({index:index,slow:e.altKey,user:true,coo:e._x-$nav.offset().left})}else{clickToShow({index:index,slow:e.altKey,user:true})}}function onArrClick(e){clickToShow({index:$arrs.index(this)?">":"<",slow:e.altKey,user:true})}smartClick($arrs,function(e){stopEvent(e);onArrClick.call(this,e)},{onStart:function(){onTouchStart();stageShaftTouchTail.control=true},onTouchEnd:onTouchEnd});smartClick($thumbArrLeft,function(e){stopEvent(e);if(opts.navtype==="thumbs"){that.show("<")}else{that.showSlide("prev")}});smartClick($thumbArrRight,function(e){stopEvent(e);if(opts.navtype==="thumbs"){that.show(">")}else{that.showSlide("next")}});function addFocusOnControls(el){addFocus(el,function(){setTimeout(function(){lockScroll($stage)},0);toggleControlsClass(false)})}$arrs.each(function(){addEnterUp(this,function(e){onArrClick.call(this,e)});addFocusOnControls(this)});addEnterUp(fullscreenIcon,function(){if($fotorama.hasClass(fullscreenClass)){that.cancelFullScreen();$stageShaft.focus()}else{that.requestFullScreen();$fullscreenIcon.focus()}});addFocusOnControls(fullscreenIcon);function reset(){setData();setOptions();if(!reset.i){reset.i=true;var _startindex=opts.startindex;activeIndex=repositionIndex=dirtyIndex=lastActiveIndex=startIndex=edgeIndex(_startindex)||0}if(size){if(changeToRtl())return;if($videoPlaying){unloadVideo($videoPlaying,true)}activeIndexes=[];detachFrames(STAGE_FRAME_KEY);reset.ok=true;that.show({index:activeIndex,time:0});that.resize()}else{that.destroy()}}function changeToRtl(){if(!changeToRtl.f===o_rtl){changeToRtl.f=o_rtl;activeIndex=size-1-activeIndex;that.reverse();return true}}$.each("load push pop shift unshift reverse sort splice".split(" "),function(i,method){that[method]=function(){data=data||[];if(method!=="load"){Array.prototype[method].apply(data,arguments)}else if(arguments[0]&&typeof arguments[0]==="object"&&arguments[0].length){data=clone(arguments[0])}reset();return that}});function ready(){if(ready.ok){ready.ok=false;triggerEvent("ready")}}reset()};$.fn.fotorama=function(opts){return this.each(function(){var that=this,$fotorama=$(this),fotoramaData=$fotorama.data(),fotorama=fotoramaData.fotorama;if(!fotorama){waitFor(function(){return!isHidden(that)},function(){fotoramaData.urtext=$fotorama.html();new $.Fotorama($fotorama,$.extend({},OPTIONS,window.fotoramaDefaults,opts,fotoramaData))})}else{fotorama.setOptions(opts,true)}})};$.Fotorama.instances=[];function calculateIndexes(){$.each($.Fotorama.instances,function(index,instance){instance.index=index})}function addInstance(instance){$.Fotorama.instances.push(instance);calculateIndexes()}function hideInstance(instance){$.Fotorama.instances.splice(instance.index,1);calculateIndexes()}$.Fotorama.cache={};$.Fotorama.measures={};$=$||{};$.Fotorama=$.Fotorama||{};$.Fotorama.jst=$.Fotorama.jst||{};$.Fotorama.jst.dots=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__nav__frame fotorama__nav__frame--dot" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__dot"></div>\r\n</div>';return __p};$.Fotorama.jst.frameCaption=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__caption" aria-hidden="true">\r\n <div class="fotorama__caption__wrap" id="'+((__t=v.labelledby)==null?"":__t)+'">'+((__t=v.caption)==null?"":__t)+"</div>\r\n</div>\r\n";return __p};$.Fotorama.jst.style=function(v){var __t,__p="",__e=_.escape;__p+=".fotorama"+((__t=v.s)==null?"":__t)+" .fotorama__nav--thumbs .fotorama__nav__frame{\r\npadding:"+((__t=v.m)==null?"":__t)+"px;\r\nheight:"+((__t=v.h)==null?"":__t)+"px}\r\n.fotorama"+((__t=v.s)==null?"":__t)+" .fotorama__thumb-border{\r\nheight:"+((__t=v.h)==null?"":__t)+"px;\r\nborder-width:"+((__t=v.b)==null?"":__t)+"px;\r\nmargin-top:"+((__t=v.m)==null?"":__t)+"px}";return __p};$.Fotorama.jst.thumb=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__nav__frame fotorama__nav__frame--thumb" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__thumb">\r\n </div>\r\n</div>';return __p}})(window,document,location,typeof jQuery!=="undefined"&&jQuery); \ No newline at end of file +fotoramaVersion="4.6.4";(function(window,document,location,$,undefined){"use strict";var _fotoramaClass="fotorama",_fullscreenClass="fotorama__fullscreen",wrapClass=_fotoramaClass+"__wrap",wrapCss2Class=wrapClass+"--css2",wrapCss3Class=wrapClass+"--css3",wrapVideoClass=wrapClass+"--video",wrapFadeClass=wrapClass+"--fade",wrapSlideClass=wrapClass+"--slide",wrapNoControlsClass=wrapClass+"--no-controls",wrapNoShadowsClass=wrapClass+"--no-shadows",wrapPanYClass=wrapClass+"--pan-y",wrapRtlClass=wrapClass+"--rtl",wrapOnlyActiveClass=wrapClass+"--only-active",wrapNoCaptionsClass=wrapClass+"--no-captions",wrapToggleArrowsClass=wrapClass+"--toggle-arrows",stageClass=_fotoramaClass+"__stage",stageFrameClass=stageClass+"__frame",stageFrameVideoClass=stageFrameClass+"--video",stageShaftClass=stageClass+"__shaft",grabClass=_fotoramaClass+"__grab",pointerClass=_fotoramaClass+"__pointer",arrClass=_fotoramaClass+"__arr",arrDisabledClass=arrClass+"--disabled",arrPrevClass=arrClass+"--prev",arrNextClass=arrClass+"--next",navClass=_fotoramaClass+"__nav",navWrapClass=navClass+"-wrap",navShaftClass=navClass+"__shaft",navShaftVerticalClass=navWrapClass+"--vertical",navShaftListClass=navWrapClass+"--list",navShafthorizontalClass=navWrapClass+"--horizontal",navDotsClass=navClass+"--dots",navThumbsClass=navClass+"--thumbs",navFrameClass=navClass+"__frame",fadeClass=_fotoramaClass+"__fade",fadeFrontClass=fadeClass+"-front",fadeRearClass=fadeClass+"-rear",shadowClass=_fotoramaClass+"__shadow",shadowsClass=shadowClass+"s",shadowsLeftClass=shadowsClass+"--left",shadowsRightClass=shadowsClass+"--right",shadowsTopClass=shadowsClass+"--top",shadowsBottomClass=shadowsClass+"--bottom",activeClass=_fotoramaClass+"__active",selectClass=_fotoramaClass+"__select",hiddenClass=_fotoramaClass+"--hidden",fullscreenClass=_fotoramaClass+"--fullscreen",fullscreenIconClass=_fotoramaClass+"__fullscreen-icon",errorClass=_fotoramaClass+"__error",loadingClass=_fotoramaClass+"__loading",loadedClass=_fotoramaClass+"__loaded",loadedFullClass=loadedClass+"--full",loadedImgClass=loadedClass+"--img",grabbingClass=_fotoramaClass+"__grabbing",imgClass=_fotoramaClass+"__img",imgFullClass=imgClass+"--full",thumbClass=_fotoramaClass+"__thumb",thumbArrLeft=thumbClass+"__arr--left",thumbArrRight=thumbClass+"__arr--right",thumbBorderClass=thumbClass+"-border",htmlClass=_fotoramaClass+"__html",videoContainerClass=_fotoramaClass+"-video-container",videoClass=_fotoramaClass+"__video",videoPlayClass=videoClass+"-play",videoCloseClass=videoClass+"-close",horizontalImageClass=_fotoramaClass+"_horizontal_ratio",verticalImageClass=_fotoramaClass+"_vertical_ratio",fotoramaSpinnerClass=_fotoramaClass+"__spinner",spinnerShowClass=fotoramaSpinnerClass+"--show";var JQUERY_VERSION=$&&$.fn.jquery.split(".");if(!JQUERY_VERSION||JQUERY_VERSION[0]<1||JQUERY_VERSION[0]==1&&JQUERY_VERSION[1]<8){throw"Fotorama requires jQuery 1.8 or later and will not run without it."}var _={};var Modernizr=function(window,document,undefined){var version="2.8.3",Modernizr={},docElement=document.documentElement,mod="modernizr",modElem=document.createElement(mod),mStyle=modElem.style,inputElem,toString={}.toString,prefixes=" -webkit- -moz- -o- -ms- ".split(" "),omPrefixes="Webkit Moz O ms",cssomPrefixes=omPrefixes.split(" "),domPrefixes=omPrefixes.toLowerCase().split(" "),tests={},inputs={},attrs={},classes=[],slice=classes.slice,featureName,injectElementWithStyles=function(rule,callback,nodes,testnames){var style,ret,node,docOverflow,div=document.createElement("div"),body=document.body,fakeBody=body||document.createElement("body");if(parseInt(nodes,10)){while(nodes--){node=document.createElement("div");node.id=testnames?testnames[nodes]:mod+(nodes+1);div.appendChild(node)}}style=["­",'<style id="s',mod,'">',rule,"</style>"].join("");div.id=mod;(body?div:fakeBody).innerHTML+=style;fakeBody.appendChild(div);if(!body){fakeBody.style.background="";fakeBody.style.overflow="hidden";docOverflow=docElement.style.overflow;docElement.style.overflow="hidden";docElement.appendChild(fakeBody)}ret=callback(div,rule);if(!body){fakeBody.parentNode.removeChild(fakeBody);docElement.style.overflow=docOverflow}else{div.parentNode.removeChild(div)}return!!ret},_hasOwnProperty={}.hasOwnProperty,hasOwnProp;if(!is(_hasOwnProperty,"undefined")&&!is(_hasOwnProperty.call,"undefined")){hasOwnProp=function(object,property){return _hasOwnProperty.call(object,property)}}else{hasOwnProp=function(object,property){return property in object&&is(object.constructor.prototype[property],"undefined")}}if(!Function.prototype.bind){Function.prototype.bind=function bind(that){var target=this;if(typeof target!="function"){throw new TypeError}var args=slice.call(arguments,1),bound=function(){if(this instanceof bound){var F=function(){};F.prototype=target.prototype;var self=new F;var result=target.apply(self,args.concat(slice.call(arguments)));if(Object(result)===result){return result}return self}else{return target.apply(that,args.concat(slice.call(arguments)))}};return bound}}function setCss(str){mStyle.cssText=str}function setCssAll(str1,str2){return setCss(prefixes.join(str1+";")+(str2||""))}function is(obj,type){return typeof obj===type}function contains(str,substr){return!!~(""+str).indexOf(substr)}function testProps(props,prefixed){for(var i in props){var prop=props[i];if(!contains(prop,"-")&&mStyle[prop]!==undefined){return prefixed=="pfx"?prop:true}}return false}function testDOMProps(props,obj,elem){for(var i in props){var item=obj[props[i]];if(item!==undefined){if(elem===false)return props[i];if(is(item,"function")){return item.bind(elem||obj)}return item}}return false}function testPropsAll(prop,prefixed,elem){var ucProp=prop.charAt(0).toUpperCase()+prop.slice(1),props=(prop+" "+cssomPrefixes.join(ucProp+" ")+ucProp).split(" ");if(is(prefixed,"string")||is(prefixed,"undefined")){return testProps(props,prefixed)}else{props=(prop+" "+domPrefixes.join(ucProp+" ")+ucProp).split(" ");return testDOMProps(props,prefixed,elem)}}tests["touch"]=function(){var bool;if("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch){bool=true}else{injectElementWithStyles(["@media (",prefixes.join("touch-enabled),("),mod,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(node){bool=node.offsetTop===9})}return bool};tests["csstransforms3d"]=function(){var ret=!!testPropsAll("perspective");if(ret&&"webkitPerspective"in docElement.style){injectElementWithStyles("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(node,rule){ret=node.offsetLeft===9&&node.offsetHeight===3})}return ret};tests["csstransitions"]=function(){return testPropsAll("transition")};for(var feature in tests){if(hasOwnProp(tests,feature)){featureName=feature.toLowerCase();Modernizr[featureName]=tests[feature]();classes.push((Modernizr[featureName]?"":"no-")+featureName)}}Modernizr.addTest=function(feature,test){if(typeof feature=="object"){for(var key in feature){if(hasOwnProp(feature,key)){Modernizr.addTest(key,feature[key])}}}else{feature=feature.toLowerCase();if(Modernizr[feature]!==undefined){return Modernizr}test=typeof test=="function"?test():test;if(typeof enableClasses!=="undefined"&&enableClasses){docElement.className+=" "+(test?"":"no-")+feature}Modernizr[feature]=test}return Modernizr};setCss("");modElem=inputElem=null;Modernizr._version=version;Modernizr._prefixes=prefixes;Modernizr._domPrefixes=domPrefixes;Modernizr._cssomPrefixes=cssomPrefixes;Modernizr.testProp=function(prop){return testProps([prop])};Modernizr.testAllProps=testPropsAll;Modernizr.testStyles=injectElementWithStyles;Modernizr.prefixed=function(prop,obj,elem){if(!obj){return testPropsAll(prop,"pfx")}else{return testPropsAll(prop,obj,elem)}};return Modernizr}(window,document);var fullScreenApi={ok:false,is:function(){return false},request:function(){},cancel:function(){},event:"",prefix:""},browserPrefixes="webkit moz o ms khtml".split(" ");if(typeof document.cancelFullScreen!="undefined"){fullScreenApi.ok=true}else{for(var i=0,il=browserPrefixes.length;i<il;i++){fullScreenApi.prefix=browserPrefixes[i];if(typeof document[fullScreenApi.prefix+"CancelFullScreen"]!="undefined"){fullScreenApi.ok=true;break}}}if(fullScreenApi.ok){fullScreenApi.event=fullScreenApi.prefix+"fullscreenchange";fullScreenApi.is=function(){switch(this.prefix){case"":return document.fullScreen;case"webkit":return document.webkitIsFullScreen;default:return document[this.prefix+"FullScreen"]}};fullScreenApi.request=function(el){return this.prefix===""?el.requestFullScreen():el[this.prefix+"RequestFullScreen"]()};fullScreenApi.cancel=function(el){return this.prefix===""?document.cancelFullScreen():document[this.prefix+"CancelFullScreen"]()}}function bez(coOrdArray){var encodedFuncName="bez_"+$.makeArray(arguments).join("_").replace(".","p");if(typeof $["easing"][encodedFuncName]!=="function"){var polyBez=function(p1,p2){var A=[null,null],B=[null,null],C=[null,null],bezCoOrd=function(t,ax){C[ax]=3*p1[ax];B[ax]=3*(p2[ax]-p1[ax])-C[ax];A[ax]=1-C[ax]-B[ax];return t*(C[ax]+t*(B[ax]+t*A[ax]))},xDeriv=function(t){return C[0]+t*(2*B[0]+3*A[0]*t)},xForT=function(t){var x=t,i=0,z;while(++i<14){z=bezCoOrd(x,0)-t;if(Math.abs(z)<.001)break;x-=z/xDeriv(x)}return x};return function(t){return bezCoOrd(xForT(t),1)}};$["easing"][encodedFuncName]=function(x,t,b,c,d){return c*polyBez([coOrdArray[0],coOrdArray[1]],[coOrdArray[2],coOrdArray[3]])(t/d)+b}}return encodedFuncName}var $WINDOW=$(window),$DOCUMENT=$(document),$HTML,$BODY,QUIRKS_FORCE=location.hash.replace("#","")==="quirks",TRANSFORMS3D=Modernizr.csstransforms3d,CSS3=TRANSFORMS3D&&!QUIRKS_FORCE,COMPAT=TRANSFORMS3D||document.compatMode==="CSS1Compat",FULLSCREEN=fullScreenApi.ok,MOBILE=navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),SLOW=!CSS3||MOBILE,MS_POINTER=navigator.msPointerEnabled,WHEEL="onwheel"in document.createElement("div")?"wheel":document.onmousewheel!==undefined?"mousewheel":"DOMMouseScroll",TOUCH_TIMEOUT=250,TRANSITION_DURATION=300,SCROLL_LOCK_TIMEOUT=1400,AUTOPLAY_INTERVAL=5e3,MARGIN=2,THUMB_SIZE=64,WIDTH=500,HEIGHT=333,STAGE_FRAME_KEY="$stageFrame",NAV_DOT_FRAME_KEY="$navDotFrame",NAV_THUMB_FRAME_KEY="$navThumbFrame",AUTO="auto",BEZIER=bez([.1,0,.25,1]),MAX_WIDTH=1200,thumbsPerSlide=1,OPTIONS={width:null,minwidth:null,maxwidth:"100%",height:null,minheight:null,maxheight:null,ratio:null,margin:MARGIN,nav:"dots",navposition:"bottom",navwidth:null,thumbwidth:THUMB_SIZE,thumbheight:THUMB_SIZE,thumbmargin:MARGIN,thumbborderwidth:MARGIN,allowfullscreen:false,transition:"slide",clicktransition:null,transitionduration:TRANSITION_DURATION,captions:true,startindex:0,loop:false,autoplay:false,stopautoplayontouch:true,keyboard:false,arrows:true,click:true,swipe:false,trackpad:false,shuffle:false,direction:"ltr",shadows:true,showcaption:true,navdir:"horizontal",navarrows:true,navtype:"thumbs"},KEYBOARD_OPTIONS={left:true,right:true,down:true,up:true,space:false,home:false,end:false};function noop(){}function minMaxLimit(value,min,max){return Math.max(isNaN(min)?-Infinity:min,Math.min(isNaN(max)?Infinity:max,value))}function readTransform(css,dir){return css.match(/ma/)&&css.match(/-?\d+(?!d)/g)[css.match(/3d/)?dir==="vertical"?13:12:dir==="vertical"?5:4]}function readPosition($el,dir){if(CSS3){return+readTransform($el.css("transform"),dir)}else{return+$el.css(dir==="vertical"?"top":"left").replace("px","")}}function getTranslate(pos,direction){var obj={};if(CSS3){switch(direction){case"vertical":obj.transform="translate3d(0, "+pos+"px,0)";break;case"list":break;default:obj.transform="translate3d("+pos+"px,0,0)";break}}else{direction==="vertical"?obj.top=pos:obj.left=pos}return obj}function getDuration(time){return{"transition-duration":time+"ms"}}function unlessNaN(value,alternative){return isNaN(value)?alternative:value}function numberFromMeasure(value,measure){return unlessNaN(+String(value).replace(measure||"px",""))}function numberFromPercent(value){return/%$/.test(value)?numberFromMeasure(value,"%"):undefined}function numberFromWhatever(value,whole){return unlessNaN(numberFromPercent(value)/100*whole,numberFromMeasure(value))}function measureIsValid(value){return(!isNaN(numberFromMeasure(value))||!isNaN(numberFromMeasure(value,"%")))&&value}function getPosByIndex(index,side,margin,baseIndex){return(index-(baseIndex||0))*(side+(margin||0))}function getIndexByPos(pos,side,margin,baseIndex){return-Math.round(pos/(side+(margin||0))-(baseIndex||0))}function bindTransitionEnd($el){var elData=$el.data();if(elData.tEnd)return;var el=$el[0],transitionEndEvent={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",msTransition:"MSTransitionEnd",transition:"transitionend"};addEvent(el,transitionEndEvent[Modernizr.prefixed("transition")],function(e){elData.tProp&&e.propertyName.match(elData.tProp)&&elData.onEndFn()});elData.tEnd=true}function afterTransition($el,property,fn,time){var ok,elData=$el.data();if(elData){elData.onEndFn=function(){if(ok)return;ok=true;clearTimeout(elData.tT);fn()};elData.tProp=property;clearTimeout(elData.tT);elData.tT=setTimeout(function(){elData.onEndFn()},time*1.5);bindTransitionEnd($el)}}function stop($el,pos){var dir=$el.navdir||"horizontal";if($el.length){var elData=$el.data();if(CSS3){$el.css(getDuration(0));elData.onEndFn=noop;clearTimeout(elData.tT)}else{$el.stop()}var lockedPos=getNumber(pos,function(){return readPosition($el,dir)});$el.css(getTranslate(lockedPos,dir));return lockedPos}}function getNumber(){var number;for(var _i=0,_l=arguments.length;_i<_l;_i++){number=_i?arguments[_i]():arguments[_i];if(typeof number==="number"){break}}return number}function edgeResistance(pos,edge){return Math.round(pos+(edge-pos)/1.5)}function getProtocol(){getProtocol.p=getProtocol.p||(location.protocol==="https:"?"https://":"http://");return getProtocol.p}function parseHref(href){var a=document.createElement("a");a.href=href;return a}function findVideoId(href,forceVideo){if(typeof href!=="string")return href;href=parseHref(href);var id,type;if(href.host.match(/youtube\.com/)&&href.search){id=href.search.split("v=")[1];if(id){var ampersandPosition=id.indexOf("&");if(ampersandPosition!==-1){id=id.substring(0,ampersandPosition)}type="youtube"}}else if(href.host.match(/youtube\.com|youtu\.be/)){id=href.pathname.replace(/^\/(embed\/|v\/)?/,"").replace(/\/.*/,"");type="youtube"}else if(href.host.match(/vimeo\.com/)){type="vimeo";id=href.pathname.replace(/^\/(video\/)?/,"").replace(/\/.*/,"")}if((!id||!type)&&forceVideo){id=href.href;type="custom"}return id?{id:id,type:type,s:href.search.replace(/^\?/,""),p:getProtocol()}:false}function getVideoThumbs(dataFrame,data,fotorama){var img,thumb,video=dataFrame.video;if(video.type==="youtube"){thumb=getProtocol()+"img.youtube.com/vi/"+video.id+"/default.jpg";img=thumb.replace(/\/default.jpg$/,"/hqdefault.jpg");dataFrame.thumbsReady=true}else if(video.type==="vimeo"){$.ajax({url:getProtocol()+"vimeo.com/api/v2/video/"+video.id+".json",dataType:"jsonp",success:function(json){dataFrame.thumbsReady=true;updateData(data,{img:json[0].thumbnail_large,thumb:json[0].thumbnail_small},dataFrame.i,fotorama)}})}else{dataFrame.thumbsReady=true}return{img:img,thumb:thumb}}function updateData(data,_dataFrame,i,fotorama){for(var _i=0,_l=data.length;_i<_l;_i++){var dataFrame=data[_i];if(dataFrame.i===i&&dataFrame.thumbsReady){var clear={videoReady:true};clear[STAGE_FRAME_KEY]=clear[NAV_THUMB_FRAME_KEY]=clear[NAV_DOT_FRAME_KEY]=false;fotorama.splice(_i,1,$.extend({},dataFrame,clear,_dataFrame));break}}}function getDataFromHtml($el){var data=[];function getDataFromImg($img,imgData,checkVideo){var $child=$img.children("img").eq(0),_imgHref=$img.attr("href"),_imgSrc=$img.attr("src"),_thumbSrc=$child.attr("src"),_video=imgData.video,video=checkVideo?findVideoId(_imgHref,_video===true):false;if(video){_imgHref=false}else{video=_video}getDimensions($img,$child,$.extend(imgData,{video:video,img:imgData.img||_imgHref||_imgSrc||_thumbSrc,thumb:imgData.thumb||_thumbSrc||_imgSrc||_imgHref}))}function getDimensions($img,$child,imgData){var separateThumbFLAG=imgData.thumb&&imgData.img!==imgData.thumb,width=numberFromMeasure(imgData.width||$img.attr("width")),height=numberFromMeasure(imgData.height||$img.attr("height"));$.extend(imgData,{width:width,height:height,thumbratio:getRatio(imgData.thumbratio||numberFromMeasure(imgData.thumbwidth||$child&&$child.attr("width")||separateThumbFLAG||width)/numberFromMeasure(imgData.thumbheight||$child&&$child.attr("height")||separateThumbFLAG||height))})}$el.children().each(function(){var $this=$(this),dataFrame=optionsToLowerCase($.extend($this.data(),{id:$this.attr("id")}));if($this.is("a, img")){getDataFromImg($this,dataFrame,true)}else if(!$this.is(":empty")){getDimensions($this,null,$.extend(dataFrame,{html:this,_html:$this.html()}))}else return;data.push(dataFrame)});return data}function isHidden(el){return el.offsetWidth===0&&el.offsetHeight===0}function isDetached(el){return!$.contains(document.documentElement,el)}function waitFor(test,fn,timeout,i){if(!waitFor.i){waitFor.i=1;waitFor.ii=[true]}i=i||waitFor.i;if(typeof waitFor.ii[i]==="undefined"){waitFor.ii[i]=true}if(test()){fn()}else{waitFor.ii[i]&&setTimeout(function(){waitFor.ii[i]&&waitFor(test,fn,timeout,i)},timeout||100)}return waitFor.i++}waitFor.stop=function(i){waitFor.ii[i]=false};function fit($el,measuresToFit){var elData=$el.data(),measures=elData.measures;if(measures&&(!elData.l||elData.l.W!==measures.width||elData.l.H!==measures.height||elData.l.r!==measures.ratio||elData.l.w!==measuresToFit.w||elData.l.h!==measuresToFit.h)){var height=minMaxLimit(measuresToFit.h,0,measures.height),width=height*measures.ratio;UTIL.setRatio($el,width,height);elData.l={W:measures.width,H:measures.height,r:measures.ratio,w:measuresToFit.w,h:measuresToFit.h}}return true}function setStyle($el,style){var el=$el[0];if(el.styleSheet){el.styleSheet.cssText=style}else{$el.html(style)}}function findShadowEdge(pos,min,max,dir){return min===max?false:dir==="vertical"?pos<=min?"top":pos>=max?"bottom":"top bottom":pos<=min?"left":pos>=max?"right":"left right"}function smartClick($el,fn,_options){_options=_options||{};$el.each(function(){var $this=$(this),thisData=$this.data(),startEvent;if(thisData.clickOn)return;thisData.clickOn=true;$.extend(touch($this,{onStart:function(e){startEvent=e;(_options.onStart||noop).call(this,e)},onMove:_options.onMove||noop,onTouchEnd:_options.onTouchEnd||noop,onEnd:function(result){if(result.moved)return;fn.call(this,startEvent)}}),{noMove:true})})}function div(classes,child){return'<div class="'+classes+'">'+(child||"")+"</div>"}function cls(className){return"."+className}function createVideoFrame(videoItem){var frame='<iframe src="'+videoItem.p+videoItem.type+".com/embed/"+videoItem.id+'" frameborder="0" allowfullscreen></iframe>';return frame}function shuffle(array){var l=array.length;while(l){var i=Math.floor(Math.random()*l--);var t=array[l];array[l]=array[i];array[i]=t}return array}function clone(array){return Object.prototype.toString.call(array)=="[object Array]"&&$.map(array,function(frame){return $.extend({},frame)})}function lockScroll($el,left,top){$el.scrollLeft(left||0).scrollTop(top||0)}function optionsToLowerCase(options){if(options){var opts={};$.each(options,function(key,value){opts[key.toLowerCase()]=value});return opts}}function getRatio(_ratio){if(!_ratio)return;var ratio=+_ratio;if(!isNaN(ratio)){return ratio}else{ratio=_ratio.split("/");return+ratio[0]/+ratio[1]||undefined}}function addEvent(el,e,fn,bool){if(!e)return;el.addEventListener?el.addEventListener(e,fn,!!bool):el.attachEvent("on"+e,fn)}function validateRestrictions(position,restriction){if(position>restriction.max){position=restriction.max}else{if(position<restriction.min){position=restriction.min}}return position}function validateSlidePos(opt,navShaftTouchTail,guessIndex,offsetNav,$guessNavFrame,$navWrap,dir){var position,size,wrapSize;if(dir==="horizontal"){size=opt.thumbwidth;wrapSize=$navWrap.width()}else{size=opt.thumbheight;wrapSize=$navWrap.height()}if((size+opt.margin)*(guessIndex+1)>=wrapSize-offsetNav){if(dir==="horizontal"){position=-$guessNavFrame.position().left}else{position=-$guessNavFrame.position().top}}else{if((size+opt.margin)*guessIndex<=Math.abs(offsetNav)){if(dir==="horizontal"){position=-$guessNavFrame.position().left+wrapSize-(size+opt.margin)}else{position=-$guessNavFrame.position().top+wrapSize-(size+opt.margin)}}else{position=offsetNav}}position=validateRestrictions(position,navShaftTouchTail);return position||0}function elIsDisabled(el){return!!el.getAttribute("disabled")}function disableAttr(FLAG,disable){if(disable){return{disabled:FLAG}}else{return{tabindex:FLAG*-1+"",disabled:FLAG}}}function addEnterUp(el,fn){addEvent(el,"keyup",function(e){elIsDisabled(el)||e.keyCode==13&&fn.call(el,e)})}function addFocus(el,fn){addEvent(el,"focus",el.onfocusin=function(e){fn.call(el,e)},true)}function stopEvent(e,stopPropagation){e.preventDefault?e.preventDefault():e.returnValue=false;stopPropagation&&e.stopPropagation&&e.stopPropagation()}function stubEvent($el,eventType){var isIOS=/ip(ad|hone|od)/i.test(window.navigator.userAgent);if(isIOS&&eventType==="touchend"){$el.on("touchend",function(e){$DOCUMENT.trigger("mouseup",e)})}$el.on(eventType,function(e){stopEvent(e,true);return false})}function getDirectionSign(forward){return forward?">":"<"}var UTIL=function(){function setRatioClass($el,wh,ht){var rateImg=wh/ht;if(rateImg<=1){$el.parent().removeClass(horizontalImageClass);$el.parent().addClass(verticalImageClass)}else{$el.parent().removeClass(verticalImageClass);$el.parent().addClass(horizontalImageClass)}}function setThumbAttr($frame,value,searchAttr){var attr=searchAttr;if(!$frame.attr(attr)&&$frame.attr(attr)!==undefined){$frame.attr(attr,value)}if($frame.find("["+attr+"]").length){$frame.find("["+attr+"]").each(function(){$(this).attr(attr,value)})}}function isExpectedCaption(frameItem,isExpected,undefined){var expected=false,frameExpected;frameItem.showCaption===undefined||frameItem.showCaption===true?frameExpected=true:frameExpected=false;if(!isExpected){return false}if(frameItem.caption&&frameExpected){expected=true}return expected}return{setRatio:setRatioClass,setThumbAttr:setThumbAttr,isExpectedCaption:isExpectedCaption}}(UTIL||{},jQuery);function slide($el,options){var elData=$el.data(),elPos=Math.round(options.pos),onEndFn=function(){if(elData&&elData.sliding){elData.sliding=false}(options.onEnd||noop)()};if(typeof options.overPos!=="undefined"&&options.overPos!==options.pos){elPos=options.overPos}var translate=$.extend(getTranslate(elPos,options.direction),options.width&&{width:options.width},options.height&&{height:options.height});if(elData&&elData.sliding){elData.sliding=true}if(CSS3){$el.css($.extend(getDuration(options.time),translate));if(options.time>10){afterTransition($el,"transform",onEndFn,options.time)}else{onEndFn()}}else{$el.stop().animate(translate,options.time,BEZIER,onEndFn)}}function fade($el1,$el2,$frames,options,fadeStack,chain){var chainedFLAG=typeof chain!=="undefined";if(!chainedFLAG){fadeStack.push(arguments);Array.prototype.push.call(arguments,fadeStack.length);if(fadeStack.length>1)return}$el1=$el1||$($el1);$el2=$el2||$($el2);var _$el1=$el1[0],_$el2=$el2[0],crossfadeFLAG=options.method==="crossfade",onEndFn=function(){if(!onEndFn.done){onEndFn.done=true;var args=(chainedFLAG||fadeStack.shift())&&fadeStack.shift();args&&fade.apply(this,args);(options.onEnd||noop)(!!args)}},time=options.time/(chain||1);$frames.removeClass(fadeRearClass+" "+fadeFrontClass);$el1.stop().addClass(fadeRearClass);$el2.stop().addClass(fadeFrontClass);crossfadeFLAG&&_$el2&&$el1.fadeTo(0,0);$el1.fadeTo(crossfadeFLAG?time:0,1,crossfadeFLAG&&onEndFn);$el2.fadeTo(time,0,onEndFn);_$el1&&crossfadeFLAG||_$el2||onEndFn()}var lastEvent,moveEventType,preventEvent,preventEventTimeout,dragDomEl;function extendEvent(e){var touch=(e.touches||[])[0]||e;e._x=touch.pageX||touch.originalEvent.pageX;e._y=touch.clientY||touch.originalEvent.clientY;e._now=$.now()}function touch($el,options){var el=$el[0],tail={},touchEnabledFLAG,startEvent,$target,controlTouch,touchFLAG,targetIsSelectFLAG,targetIsLinkFlag,tolerance,moved;function onStart(e){$target=$(e.target);tail.checked=targetIsSelectFLAG=targetIsLinkFlag=moved=false;if(touchEnabledFLAG||tail.flow||e.touches&&e.touches.length>1||e.which>1||lastEvent&&lastEvent.type!==e.type&&preventEvent||(targetIsSelectFLAG=options.select&&$target.is(options.select,el)))return targetIsSelectFLAG;touchFLAG=e.type==="touchstart";targetIsLinkFlag=$target.is("a, a *",el);controlTouch=tail.control;tolerance=tail.noMove||tail.noSwipe||controlTouch?16:!tail.snap?4:0;extendEvent(e);startEvent=lastEvent=e;moveEventType=e.type.replace(/down|start/,"move").replace(/Down/,"Move");(options.onStart||noop).call(el,e,{control:controlTouch,$target:$target});touchEnabledFLAG=tail.flow=true;if(!touchFLAG||tail.go)stopEvent(e)}function onMove(e){if(e.touches&&e.touches.length>1||MS_POINTER&&!e.isPrimary||moveEventType!==e.type||!touchEnabledFLAG){touchEnabledFLAG&&onEnd();(options.onTouchEnd||noop)();return}extendEvent(e);var xDiff=Math.abs(e._x-startEvent._x),yDiff=Math.abs(e._y-startEvent._y),xyDiff=xDiff-yDiff,xWin=(tail.go||tail.x||xyDiff>=0)&&!tail.noSwipe,yWin=xyDiff<0;if(touchFLAG&&!tail.checked){if(touchEnabledFLAG=xWin){stopEvent(e)}}else{stopEvent(e);if(movedEnough(xDiff,yDiff)){(options.onMove||noop).call(el,e,{touch:touchFLAG})}}if(!moved&&movedEnough(xDiff,yDiff)&&Math.sqrt(Math.pow(xDiff,2)+Math.pow(yDiff,2))>tolerance){moved=true}tail.checked=tail.checked||xWin||yWin}function movedEnough(xDiff,yDiff){return xDiff>yDiff&&xDiff>1.5}function onEnd(e){(options.onTouchEnd||noop)();var _touchEnabledFLAG=touchEnabledFLAG;tail.control=touchEnabledFLAG=false;if(_touchEnabledFLAG){tail.flow=false}if(!_touchEnabledFLAG||targetIsLinkFlag&&!tail.checked)return;e&&stopEvent(e);preventEvent=true;clearTimeout(preventEventTimeout);preventEventTimeout=setTimeout(function(){preventEvent=false},1e3);(options.onEnd||noop).call(el,{moved:moved,$target:$target,control:controlTouch,touch:touchFLAG,startEvent:startEvent,aborted:!e||e.type==="MSPointerCancel"})}function onOtherStart(){if(tail.flow)return;tail.flow=true}function onOtherEnd(){if(!tail.flow)return;tail.flow=false}if(MS_POINTER){addEvent(el,"MSPointerDown",onStart);addEvent(document,"MSPointerMove",onMove);addEvent(document,"MSPointerCancel",onEnd);addEvent(document,"MSPointerUp",onEnd)}else{addEvent(el,"touchstart",onStart);addEvent(el,"touchmove",onMove);addEvent(el,"touchend",onEnd);addEvent(document,"touchstart",onOtherStart);addEvent(document,"touchend",onOtherEnd);addEvent(document,"touchcancel",onOtherEnd);$WINDOW.on("scroll",onOtherEnd);$el.on("mousedown pointerdown",onStart);$DOCUMENT.on("mousemove pointermove",onMove).on("mouseup pointerup",onEnd)}if(Modernizr.touch){dragDomEl="a"}else{dragDomEl="div"}$el.on("click",dragDomEl,function(e){tail.checked&&stopEvent(e)});return tail}function moveOnTouch($el,options){var el=$el[0],elData=$el.data(),tail={},startCoo,coo,startElPos,moveElPos,edge,moveTrack,startTime,endTime,min,max,snap,dir,slowFLAG,controlFLAG,moved,tracked;function startTracking(e,noStop){tracked=true;startCoo=coo=dir==="vertical"?e._y:e._x;startTime=e._now;moveTrack=[[startTime,startCoo]];startElPos=moveElPos=tail.noMove||noStop?0:stop($el,(options.getPos||noop)());(options.onStart||noop).call(el,e)}function onStart(e,result){min=tail.min;max=tail.max;snap=tail.snap,dir=tail.direction||"horizontal",$el.navdir=dir;slowFLAG=e.altKey;tracked=moved=false;controlFLAG=result.control;if(!controlFLAG&&!elData.sliding){startTracking(e)}}function onMove(e,result){if(!tail.noSwipe){if(!tracked){startTracking(e)}coo=dir==="vertical"?e._y:e._x;moveTrack.push([e._now,coo]);moveElPos=startElPos-(startCoo-coo);edge=findShadowEdge(moveElPos,min,max,dir);if(moveElPos<=min){moveElPos=edgeResistance(moveElPos,min)}else if(moveElPos>=max){moveElPos=edgeResistance(moveElPos,max)}if(!tail.noMove){$el.css(getTranslate(moveElPos,dir));if(!moved){moved=true;result.touch||MS_POINTER||$el.addClass(grabbingClass)}(options.onMove||noop).call(el,e,{pos:moveElPos,edge:edge})}}}function onEnd(result){if(tail.noSwipe&&result.moved)return;if(!tracked){startTracking(result.startEvent,true)}result.touch||MS_POINTER||$el.removeClass(grabbingClass);endTime=$.now();var _backTimeIdeal=endTime-TOUCH_TIMEOUT,_backTime,_timeDiff,_timeDiffLast,backTime=null,backCoo,virtualPos,limitPos,newPos,overPos,time=TRANSITION_DURATION,speed,friction=options.friction;for(var _i=moveTrack.length-1;_i>=0;_i--){_backTime=moveTrack[_i][0];_timeDiff=Math.abs(_backTime-_backTimeIdeal);if(backTime===null||_timeDiff<_timeDiffLast){backTime=_backTime;backCoo=moveTrack[_i][1]}else if(backTime===_backTimeIdeal||_timeDiff>_timeDiffLast){break}_timeDiffLast=_timeDiff}newPos=minMaxLimit(moveElPos,min,max);var cooDiff=backCoo-coo,forwardFLAG=cooDiff>=0,timeDiff=endTime-backTime,longTouchFLAG=timeDiff>TOUCH_TIMEOUT,swipeFLAG=!longTouchFLAG&&moveElPos!==startElPos&&newPos===moveElPos;if(snap){newPos=minMaxLimit(Math[swipeFLAG?forwardFLAG?"floor":"ceil":"round"](moveElPos/snap)*snap,min,max);min=max=newPos}if(swipeFLAG&&(snap||newPos===moveElPos)){speed=-(cooDiff/timeDiff);time*=minMaxLimit(Math.abs(speed),options.timeLow,options.timeHigh);virtualPos=Math.round(moveElPos+speed*time/friction);if(!snap){newPos=virtualPos}if(!forwardFLAG&&virtualPos>max||forwardFLAG&&virtualPos<min){limitPos=forwardFLAG?min:max;overPos=virtualPos-limitPos;if(!snap){newPos=limitPos}overPos=minMaxLimit(newPos+overPos*.03,limitPos-50,limitPos+50);time=Math.abs((moveElPos-overPos)/(speed/friction))}}time*=slowFLAG?10:1;(options.onEnd||noop).call(el,$.extend(result,{moved:result.moved||longTouchFLAG&&snap,pos:moveElPos,newPos:newPos,overPos:overPos,time:time,dir:dir}))}tail=$.extend(touch(options.$wrap,$.extend({},options,{onStart:onStart,onMove:onMove,onEnd:onEnd})),tail);return tail}function wheel($el,options){var el=$el[0],lockFLAG,lastDirection,lastNow,tail={prevent:{}};addEvent(el,WHEEL,function(e){var yDelta=e.wheelDeltaY||-1*e.deltaY||0,xDelta=e.wheelDeltaX||-1*e.deltaX||0,xWin=Math.abs(xDelta)&&!Math.abs(yDelta),direction=getDirectionSign(xDelta<0),sameDirection=lastDirection===direction,now=$.now(),tooFast=now-lastNow<TOUCH_TIMEOUT;lastDirection=direction;lastNow=now;if(!xWin||!tail.ok||tail.prevent[direction]&&!lockFLAG){return}else{stopEvent(e,true);if(lockFLAG&&sameDirection&&tooFast){return}}if(options.shift){lockFLAG=true;clearTimeout(tail.t);tail.t=setTimeout(function(){lockFLAG=false},SCROLL_LOCK_TIMEOUT)}(options.onEnd||noop)(e,options.shift?direction:xDelta)});return tail}jQuery.Fotorama=function($fotorama,opts){$HTML=$("html");$BODY=$("body");var that=this,stamp=$.now(),stampClass=_fotoramaClass+stamp,fotorama=$fotorama[0],data,dataFrameCount=1,fotoramaData=$fotorama.data(),size,$style=$("<style></style>"),$anchor=$(div(hiddenClass)),$wrap=$fotorama.find(cls(wrapClass)),$stage=$wrap.find(cls(stageClass)),stage=$stage[0],$stageShaft=$fotorama.find(cls(stageShaftClass)),$stageFrame=$(),$arrPrev=$fotorama.find(cls(arrPrevClass)),$arrNext=$fotorama.find(cls(arrNextClass)),$arrs=$fotorama.find(cls(arrClass)),$navWrap=$fotorama.find(cls(navWrapClass)),$nav=$navWrap.find(cls(navClass)),$navShaft=$nav.find(cls(navShaftClass)),$navFrame,$navDotFrame=$(),$navThumbFrame=$(),stageShaftData=$stageShaft.data(),navShaftData=$navShaft.data(),$thumbBorder=$fotorama.find(cls(thumbBorderClass)),$thumbArrLeft=$fotorama.find(cls(thumbArrLeft)),$thumbArrRight=$fotorama.find(cls(thumbArrRight)),$fullscreenIcon=$fotorama.find(cls(fullscreenIconClass)),fullscreenIcon=$fullscreenIcon[0],$videoPlay=$(div(videoPlayClass)),$videoClose=$fotorama.find(cls(videoCloseClass)),videoClose=$videoClose[0],$spinner=$fotorama.find(cls(fotoramaSpinnerClass)),$videoPlaying,activeIndex=false,activeFrame,activeIndexes,repositionIndex,dirtyIndex,lastActiveIndex,prevIndex,nextIndex,nextAutoplayIndex,startIndex,o_loop,o_nav,o_navThumbs,o_navTop,o_allowFullScreen,o_nativeFullScreen,o_fade,o_thumbSide,o_thumbSide2,o_transitionDuration,o_transition,o_shadows,o_rtl,o_keyboard,lastOptions={},measures={},measuresSetFLAG,stageShaftTouchTail={},stageWheelTail={},navShaftTouchTail={},navWheelTail={},scrollTop,scrollLeft,showedFLAG,pausedAutoplayFLAG,stoppedAutoplayFLAG,toDeactivate={},toDetach={},measuresStash,touchedFLAG,hoverFLAG,navFrameKey,stageLeft=0,fadeStack=[];$wrap[STAGE_FRAME_KEY]=$('<div class="'+stageFrameClass+'"></div>');$wrap[NAV_THUMB_FRAME_KEY]=$($.Fotorama.jst.thumb());$wrap[NAV_DOT_FRAME_KEY]=$($.Fotorama.jst.dots());toDeactivate[STAGE_FRAME_KEY]=[];toDeactivate[NAV_THUMB_FRAME_KEY]=[];toDeactivate[NAV_DOT_FRAME_KEY]=[];toDetach[STAGE_FRAME_KEY]={};$wrap.addClass(CSS3?wrapCss3Class:wrapCss2Class);fotoramaData.fotorama=this;function checkForVideo(){$.each(data,function(i,dataFrame){if(!dataFrame.i){dataFrame.i=dataFrameCount++;var video=findVideoId(dataFrame.video,true);if(video){var thumbs={};dataFrame.video=video;if(!dataFrame.img&&!dataFrame.thumb){thumbs=getVideoThumbs(dataFrame,data,that)}else{dataFrame.thumbsReady=true}updateData(data,{img:thumbs.img,thumb:thumbs.thumb},dataFrame.i,that)}}})}function allowKey(key){return o_keyboard[key]}function setStagePosition(){if($stage!==undefined){if(opts.navdir=="vertical"){var padding=opts.thumbwidth+opts.thumbmargin;$stage.css("left",padding);$arrNext.css("right",padding);$fullscreenIcon.css("right",padding);$wrap.css("width",$wrap.css("width")+padding);$stageShaft.css("max-width",$wrap.width()-padding)}else{$stage.css("left","");$arrNext.css("right","");$fullscreenIcon.css("right","");$wrap.css("width",$wrap.css("width")+padding);$stageShaft.css("max-width","")}}}function bindGlobalEvents(FLAG){var keydownCommon="keydown."+_fotoramaClass,localStamp=_fotoramaClass+stamp,keydownLocal="keydown."+localStamp,keyupLocal="keyup."+localStamp,resizeLocal="resize."+localStamp+" "+"orientationchange."+localStamp,showParams;if(FLAG){$DOCUMENT.on(keydownLocal,function(e){var catched,index;if($videoPlaying&&e.keyCode===27){catched=true;unloadVideo($videoPlaying,true,true)}else if(that.fullScreen||opts.keyboard&&!that.index){if(e.keyCode===27){catched=true;that.cancelFullScreen()}else if(e.shiftKey&&e.keyCode===32&&allowKey("space")||!e.altKey&&!e.metaKey&&e.keyCode===37&&allowKey("left")||e.keyCode===38&&allowKey("up")&&$(":focus").attr("data-gallery-role")){that.longPress.progress();index="<"}else if(e.keyCode===32&&allowKey("space")||!e.altKey&&!e.metaKey&&e.keyCode===39&&allowKey("right")||e.keyCode===40&&allowKey("down")&&$(":focus").attr("data-gallery-role")){that.longPress.progress();index=">"}else if(e.keyCode===36&&allowKey("home")){that.longPress.progress();index="<<"}else if(e.keyCode===35&&allowKey("end")){that.longPress.progress();index=">>"}}(catched||index)&&stopEvent(e);showParams={index:index,slow:e.altKey,user:true};index&&(that.longPress.inProgress?that.showWhileLongPress(showParams):that.show(showParams))});if(FLAG){$DOCUMENT.on(keyupLocal,function(e){if(that.longPress.inProgress){that.showEndLongPress({user:true})}that.longPress.reset()})}if(!that.index){$DOCUMENT.off(keydownCommon).on(keydownCommon,"textarea, input, select",function(e){!$BODY.hasClass(_fullscreenClass)&&e.stopPropagation()})}$WINDOW.on(resizeLocal,that.resize)}else{$DOCUMENT.off(keydownLocal);$WINDOW.off(resizeLocal)}}function appendElements(FLAG){if(FLAG===appendElements.f)return;if(FLAG){$fotorama.addClass(_fotoramaClass+" "+stampClass).before($anchor).before($style);addInstance(that)}else{$anchor.detach();$style.detach();$fotorama.html(fotoramaData.urtext).removeClass(stampClass);hideInstance(that)}bindGlobalEvents(FLAG);appendElements.f=FLAG}function setData(){data=that.data=data||clone(opts.data)||getDataFromHtml($fotorama);size=that.size=data.length;ready.ok&&opts.shuffle&&shuffle(data);checkForVideo();activeIndex=limitIndex(activeIndex);size&&appendElements(true)}function stageNoMove(){var _noMove=size<2||$videoPlaying;stageShaftTouchTail.noMove=_noMove||o_fade;stageShaftTouchTail.noSwipe=_noMove||!opts.swipe;!o_transition&&$stageShaft.toggleClass(grabClass,!opts.click&&!stageShaftTouchTail.noMove&&!stageShaftTouchTail.noSwipe);MS_POINTER&&$wrap.toggleClass(wrapPanYClass,!stageShaftTouchTail.noSwipe)}function setAutoplayInterval(interval){if(interval===true)interval="";opts.autoplay=Math.max(+interval||AUTOPLAY_INTERVAL,o_transitionDuration*1.5)}function updateThumbArrow(opt){if(opt.navarrows&&opt.nav==="thumbs"){$thumbArrLeft.show();$thumbArrRight.show()}else{$thumbArrLeft.hide();$thumbArrRight.hide()}}function getThumbsInSlide($el,opts){return Math.floor($wrap.width()/(opts.thumbwidth+opts.thumbmargin))}function setOptions(){if(!opts.nav||opts.nav==="dots"){opts.navdir="horizontal"}that.options=opts=optionsToLowerCase(opts);thumbsPerSlide=getThumbsInSlide($wrap,opts);o_fade=opts.transition==="crossfade"||opts.transition==="dissolve";o_loop=opts.loop&&(size>2||o_fade&&(!o_transition||o_transition!=="slide"));o_transitionDuration=+opts.transitionduration||TRANSITION_DURATION;o_rtl=opts.direction==="rtl";o_keyboard=$.extend({},opts.keyboard&&KEYBOARD_OPTIONS,opts.keyboard);updateThumbArrow(opts);var classes={add:[],remove:[]};function addOrRemoveClass(FLAG,value){classes[FLAG?"add":"remove"].push(value)}if(size>1){o_nav=opts.nav;o_navTop=opts.navposition==="top";classes.remove.push(selectClass);$arrs.toggle(opts.arrows)}else{o_nav=false;$arrs.hide()}arrsUpdate();stageWheelUpdate();thumbArrUpdate();if(opts.autoplay)setAutoplayInterval(opts.autoplay);o_thumbSide=numberFromMeasure(opts.thumbwidth)||THUMB_SIZE;o_thumbSide2=numberFromMeasure(opts.thumbheight)||THUMB_SIZE;stageWheelTail.ok=navWheelTail.ok=opts.trackpad&&!SLOW;stageNoMove();extendMeasures(opts,[measures]);o_navThumbs=o_nav==="thumbs";if($navWrap.filter(":hidden")&&!!o_nav){$navWrap.show()}if(o_navThumbs){frameDraw(size,"navThumb");$navFrame=$navThumbFrame;navFrameKey=NAV_THUMB_FRAME_KEY;setStyle($style,$.Fotorama.jst.style({w:o_thumbSide,h:o_thumbSide2,b:opts.thumbborderwidth,m:opts.thumbmargin,s:stamp,q:!COMPAT}));$nav.addClass(navThumbsClass).removeClass(navDotsClass)}else if(o_nav==="dots"){frameDraw(size,"navDot");$navFrame=$navDotFrame;navFrameKey=NAV_DOT_FRAME_KEY;$nav.addClass(navDotsClass).removeClass(navThumbsClass)}else{$navWrap.hide();o_nav=false;$nav.removeClass(navThumbsClass+" "+navDotsClass)}if(o_nav){if(o_navTop){$navWrap.insertBefore($stage)}else{$navWrap.insertAfter($stage)}frameAppend.nav=false;frameAppend($navFrame,$navShaft,"nav")}o_allowFullScreen=opts.allowfullscreen;if(o_allowFullScreen){$fullscreenIcon.prependTo($stage);o_nativeFullScreen=FULLSCREEN&&o_allowFullScreen==="native";stubEvent($fullscreenIcon,"touchend")}else{$fullscreenIcon.detach();o_nativeFullScreen=false}addOrRemoveClass(o_fade,wrapFadeClass);addOrRemoveClass(!o_fade,wrapSlideClass);addOrRemoveClass(!opts.captions,wrapNoCaptionsClass);addOrRemoveClass(o_rtl,wrapRtlClass);addOrRemoveClass(opts.arrows,wrapToggleArrowsClass);o_shadows=opts.shadows&&!SLOW;addOrRemoveClass(!o_shadows,wrapNoShadowsClass);$wrap.addClass(classes.add.join(" ")).removeClass(classes.remove.join(" "));lastOptions=$.extend({},opts);setStagePosition()}function normalizeIndex(index){return index<0?(size+index%size)%size:index>=size?index%size:index}function limitIndex(index){return minMaxLimit(index,0,size-1)}function edgeIndex(index){return o_loop?normalizeIndex(index):limitIndex(index)}function getPrevIndex(index){return index>0||o_loop?index-1:false}function getNextIndex(index){return index<size-1||o_loop?index+1:false}function setStageShaftMinmaxAndSnap(){stageShaftTouchTail.min=o_loop?-Infinity:-getPosByIndex(size-1,measures.w,opts.margin,repositionIndex);stageShaftTouchTail.max=o_loop?Infinity:-getPosByIndex(0,measures.w,opts.margin,repositionIndex);stageShaftTouchTail.snap=measures.w+opts.margin}function setNavShaftMinMax(){var isVerticalDir=opts.navdir==="vertical";var param=isVerticalDir?$navShaft.height():$navShaft.width();var mainParam=isVerticalDir?measures.h:measures.nw;navShaftTouchTail.min=Math.min(0,mainParam-param);navShaftTouchTail.max=0;navShaftTouchTail.direction=opts.navdir;$navShaft.toggleClass(grabClass,!(navShaftTouchTail.noMove=navShaftTouchTail.min===navShaftTouchTail.max))}function eachIndex(indexes,type,fn){if(typeof indexes==="number"){indexes=new Array(indexes);var rangeFLAG=true}return $.each(indexes,function(i,index){if(rangeFLAG)index=i;if(typeof index==="number"){var dataFrame=data[normalizeIndex(index)];if(dataFrame){var key="$"+type+"Frame",$frame=dataFrame[key];fn.call(this,i,index,dataFrame,$frame,key,$frame&&$frame.data())}}})}function setMeasures(width,height,ratio,index){if(!measuresSetFLAG||measuresSetFLAG==="*"&&index===startIndex){width=measureIsValid(opts.width)||measureIsValid(width)||WIDTH;height=measureIsValid(opts.height)||measureIsValid(height)||HEIGHT;that.resize({width:width,ratio:opts.ratio||ratio||width/height},0,index!==startIndex&&"*")}}function loadImg(indexes,type,specialMeasures,again){eachIndex(indexes,type,function(i,index,dataFrame,$frame,key,frameData){if(!$frame)return;var fullFLAG=that.fullScreen&&!frameData.$full&&type==="stage";if(frameData.$img&&!again&&!fullFLAG)return;var img=new Image,$img=$(img),imgData=$img.data();frameData[fullFLAG?"$full":"$img"]=$img;var srcKey=type==="stage"?fullFLAG?"full":"img":"thumb",src=dataFrame[srcKey],dummy=fullFLAG?dataFrame["img"]:dataFrame[type==="stage"?"thumb":"img"];if(type==="navThumb")$frame=frameData.$wrap;function triggerTriggerEvent(event){var _index=normalizeIndex(index);triggerEvent(event,{index:_index,src:src,frame:data[_index]})}function error(){$img.remove();$.Fotorama.cache[src]="error";if((!dataFrame.html||type!=="stage")&&dummy&&dummy!==src){dataFrame[srcKey]=src=dummy;frameData.$full=null;loadImg([index],type,specialMeasures,true)}else{if(src&&!dataFrame.html&&!fullFLAG){$frame.trigger("f:error").removeClass(loadingClass).addClass(errorClass);triggerTriggerEvent("error")}else if(type==="stage"){$frame.trigger("f:load").removeClass(loadingClass+" "+errorClass).addClass(loadedClass);triggerTriggerEvent("load");setMeasures()}frameData.state="error";if(size>1&&data[index]===dataFrame&&!dataFrame.html&&!dataFrame.deleted&&!dataFrame.video&&!fullFLAG){dataFrame.deleted=true;that.splice(index,1)}}}function loaded(){$.Fotorama.measures[src]=imgData.measures=$.Fotorama.measures[src]||{width:img.width,height:img.height,ratio:img.width/img.height};setMeasures(imgData.measures.width,imgData.measures.height,imgData.measures.ratio,index);$img.off("load error").addClass(""+(fullFLAG?imgFullClass:imgClass)).attr("aria-hidden","false").prependTo($frame);if($frame.hasClass(stageFrameClass)&&!$frame.hasClass(videoContainerClass)){$frame.attr("href",$img.attr("src"))}fit($img,($.isFunction(specialMeasures)?specialMeasures():specialMeasures)||measures);$.Fotorama.cache[src]=frameData.state="loaded";setTimeout(function(){$frame.trigger("f:load").removeClass(loadingClass+" "+errorClass).addClass(loadedClass+" "+(fullFLAG?loadedFullClass:loadedImgClass));if(type==="stage"){triggerTriggerEvent("load")}else if(dataFrame.thumbratio===AUTO||!dataFrame.thumbratio&&opts.thumbratio===AUTO){dataFrame.thumbratio=imgData.measures.ratio;reset()}},0)}if(!src){error();return}function waitAndLoad(){var _i=10;waitFor(function(){return!touchedFLAG||!_i--&&!SLOW},function(){loaded()})}if(!$.Fotorama.cache[src]){$.Fotorama.cache[src]="*";$img.on("load",waitAndLoad).on("error",error)}else{(function justWait(){if($.Fotorama.cache[src]==="error"){error()}else if($.Fotorama.cache[src]==="loaded"){setTimeout(waitAndLoad,0)}else{setTimeout(justWait,100)}})()}frameData.state="";img.src=src;if(frameData.data.caption){img.alt=frameData.data.caption||""}if(frameData.data.full){$(img).data("original",frameData.data.full)}if(UTIL.isExpectedCaption(dataFrame,opts.showcaption)){$(img).attr("aria-labelledby",dataFrame.labelledby)}})}function updateFotoramaState(){var $frame=activeFrame[STAGE_FRAME_KEY];if($frame&&!$frame.data().state){$spinner.addClass(spinnerShowClass);$frame.on("f:load f:error",function(){$frame.off("f:load f:error");$spinner.removeClass(spinnerShowClass)})}}function addNavFrameEvents(frame){addEnterUp(frame,onNavFrameClick);addFocus(frame,function(){setTimeout(function(){lockScroll($nav)},0);slideNavShaft({time:o_transitionDuration,guessIndex:$(this).data().eq,minMax:navShaftTouchTail})})}function frameDraw(indexes,type){eachIndex(indexes,type,function(i,index,dataFrame,$frame,key,frameData){if($frame)return;$frame=dataFrame[key]=$wrap[key].clone();frameData=$frame.data();frameData.data=dataFrame;var frame=$frame[0],labelledbyValue="labelledby"+$.now();if(type==="stage"){if(dataFrame.html){$('<div class="'+htmlClass+'"></div>').append(dataFrame._html?$(dataFrame.html).removeAttr("id").html(dataFrame._html):dataFrame.html).appendTo($frame)}if(dataFrame.id){labelledbyValue=dataFrame.id||labelledbyValue}dataFrame.labelledby=labelledbyValue;if(UTIL.isExpectedCaption(dataFrame,opts.showcaption)){$($.Fotorama.jst.frameCaption({caption:dataFrame.caption,labelledby:labelledbyValue})).appendTo($frame)}dataFrame.video&&$frame.addClass(stageFrameVideoClass).append($videoPlay.clone());addFocus(frame,function(){setTimeout(function(){lockScroll($stage)},0);clickToShow({index:frameData.eq,user:true})});$stageFrame=$stageFrame.add($frame)}else if(type==="navDot"){addNavFrameEvents(frame);$navDotFrame=$navDotFrame.add($frame)}else if(type==="navThumb"){addNavFrameEvents(frame);frameData.$wrap=$frame.children(":first");$navThumbFrame=$navThumbFrame.add($frame);if(dataFrame.video){frameData.$wrap.append($videoPlay.clone())}}})}function callFit($img,measuresToFit){return $img&&$img.length&&fit($img,measuresToFit)}function stageFramePosition(indexes){eachIndex(indexes,"stage",function(i,index,dataFrame,$frame,key,frameData){if(!$frame)return;var normalizedIndex=normalizeIndex(index);frameData.eq=normalizedIndex;toDetach[STAGE_FRAME_KEY][normalizedIndex]=$frame.css($.extend({left:o_fade?0:getPosByIndex(index,measures.w,opts.margin,repositionIndex)},o_fade&&getDuration(0)));if(isDetached($frame[0])){$frame.appendTo($stageShaft);unloadVideo(dataFrame.$video)}callFit(frameData.$img,measures);callFit(frameData.$full,measures);if($frame.hasClass(stageFrameClass)&&!($frame.attr("aria-hidden")==="false"&&$frame.hasClass(activeClass))){$frame.attr("aria-hidden","true")}})}function thumbsDraw(pos,loadFLAG){var leftLimit,rightLimit,exceedLimit;if(o_nav!=="thumbs"||isNaN(pos))return;leftLimit=-pos;rightLimit=-pos+measures.nw;if(opts.navdir==="vertical"){pos=pos-opts.thumbheight;rightLimit=-pos+measures.h}$navThumbFrame.each(function(){var $this=$(this),thisData=$this.data(),eq=thisData.eq,getSpecialMeasures=function(){return{h:o_thumbSide2,w:thisData.w}},specialMeasures=getSpecialMeasures(),exceedLimit=opts.navdir==="vertical"?thisData.t>rightLimit:thisData.l>rightLimit;specialMeasures.w=thisData.w;if(thisData.l+thisData.w<leftLimit||exceedLimit||callFit(thisData.$img,specialMeasures))return;loadFLAG&&loadImg([eq],"navThumb",getSpecialMeasures)})}function frameAppend($frames,$shaft,type){if(!frameAppend[type]){var thumbsFLAG=type==="nav"&&o_navThumbs,left=0,top=0;$shaft.append($frames.filter(function(){var actual,$this=$(this),frameData=$this.data();for(var _i=0,_l=data.length;_i<_l;_i++){if(frameData.data===data[_i]){actual=true;frameData.eq=_i;break}}return actual||$this.remove()&&false}).sort(function(a,b){return $(a).data().eq-$(b).data().eq}).each(function(){var $this=$(this),frameData=$this.data();UTIL.setThumbAttr($this,frameData.data.caption,"aria-label")}).each(function(){if(!thumbsFLAG)return;var $this=$(this),frameData=$this.data(),thumbwidth=Math.round(o_thumbSide2*frameData.data.thumbratio)||o_thumbSide,thumbheight=Math.round(o_thumbSide/frameData.data.thumbratio)||o_thumbSide2;frameData.t=top;frameData.h=thumbheight;frameData.l=left;frameData.w=thumbwidth;$this.css({width:thumbwidth});top+=thumbheight+opts.thumbmargin;left+=thumbwidth+opts.thumbmargin}));frameAppend[type]=true}}function getDirection(x){return x-stageLeft>measures.w/3}function disableDirrection(i){return!o_loop&&(!(activeIndex+i)||!(activeIndex-size+i))&&!$videoPlaying}function arrsUpdate(){var disablePrev=disableDirrection(0),disableNext=disableDirrection(1);$arrPrev.toggleClass(arrDisabledClass,disablePrev).attr(disableAttr(disablePrev,false));$arrNext.toggleClass(arrDisabledClass,disableNext).attr(disableAttr(disableNext,false))}function thumbArrUpdate(){var isLeftDisable=false,isRightDisable=false;if(opts.navtype==="thumbs"&&!opts.loop){activeIndex==0?isLeftDisable=true:isLeftDisable=false;activeIndex==opts.data.length-1?isRightDisable=true:isRightDisable=false}if(opts.navtype==="slides"){var pos=readPosition($navShaft,opts.navdir);pos>=navShaftTouchTail.max?isLeftDisable=true:isLeftDisable=false;pos<=navShaftTouchTail.min?isRightDisable=true:isRightDisable=false}$thumbArrLeft.toggleClass(arrDisabledClass,isLeftDisable).attr(disableAttr(isLeftDisable,true));$thumbArrRight.toggleClass(arrDisabledClass,isRightDisable).attr(disableAttr(isRightDisable,true))}function stageWheelUpdate(){if(stageWheelTail.ok){stageWheelTail.prevent={"<":disableDirrection(0),">":disableDirrection(1)}}}function getNavFrameBounds($navFrame){var navFrameData=$navFrame.data(),left,top,width,height;if(o_navThumbs){left=navFrameData.l;top=navFrameData.t;width=navFrameData.w;height=navFrameData.h}else{left=$navFrame.position().left;width=$navFrame.width()}var horizontalBounds={c:left+width/2,min:-left+opts.thumbmargin*10,max:-left+measures.w-width-opts.thumbmargin*10};var verticalBounds={c:top+height/2,min:-top+opts.thumbmargin*10,max:-top+measures.h-height-opts.thumbmargin*10};return opts.navdir==="vertical"?verticalBounds:horizontalBounds}function slideThumbBorder(time){var navFrameData=activeFrame[navFrameKey].data();slide($thumbBorder,{time:time*1.2,pos:opts.navdir==="vertical"?navFrameData.t:navFrameData.l,width:navFrameData.w,height:navFrameData.h,direction:opts.navdir})}function slideNavShaft(options){var $guessNavFrame=data[options.guessIndex][navFrameKey],typeOfAnimation=opts.navtype;var overflowFLAG,time,minMax,boundTop,boundLeft,l,pos,x;if($guessNavFrame){if(typeOfAnimation==="thumbs"){overflowFLAG=navShaftTouchTail.min!==navShaftTouchTail.max;minMax=options.minMax||overflowFLAG&&getNavFrameBounds(activeFrame[navFrameKey]);boundTop=overflowFLAG&&(options.keep&&slideNavShaft.t?slideNavShaft.l:minMaxLimit((options.coo||measures.nw/2)-getNavFrameBounds($guessNavFrame).c,minMax.min,minMax.max));boundLeft=overflowFLAG&&(options.keep&&slideNavShaft.l?slideNavShaft.l:minMaxLimit((options.coo||measures.nw/2)-getNavFrameBounds($guessNavFrame).c,minMax.min,minMax.max));l=opts.navdir==="vertical"?boundTop:boundLeft;pos=overflowFLAG&&minMaxLimit(l,navShaftTouchTail.min,navShaftTouchTail.max)||0;time=options.time*1.1;slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:function(){thumbsDraw(pos,true);thumbArrUpdate()}});setShadow($nav,findShadowEdge(pos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir));slideNavShaft.l=l}else{x=readPosition($navShaft,opts.navdir);time=options.time*1.11;pos=validateSlidePos(opts,navShaftTouchTail,options.guessIndex,x,$guessNavFrame,$navWrap,opts.navdir);slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:function(){thumbsDraw(pos,true);thumbArrUpdate()}});setShadow($nav,findShadowEdge(pos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir))}}}function navUpdate(){deactivateFrames(navFrameKey);toDeactivate[navFrameKey].push(activeFrame[navFrameKey].addClass(activeClass).attr("data-active",true))}function deactivateFrames(key){var _toDeactivate=toDeactivate[key];while(_toDeactivate.length){_toDeactivate.shift().removeClass(activeClass).attr("data-active",false)}}function detachFrames(key){var _toDetach=toDetach[key];$.each(activeIndexes,function(i,index){delete _toDetach[normalizeIndex(index)]});$.each(_toDetach,function(index,$frame){delete _toDetach[index];$frame.detach()})}function stageShaftReposition(skipOnEnd){repositionIndex=dirtyIndex=activeIndex;var $frame=activeFrame[STAGE_FRAME_KEY];if($frame){deactivateFrames(STAGE_FRAME_KEY);toDeactivate[STAGE_FRAME_KEY].push($frame.addClass(activeClass).attr("data-active",true));if($frame.hasClass(stageFrameClass)){$frame.attr("aria-hidden","false")}skipOnEnd||that.showStage.onEnd(true);stop($stageShaft,0,true);detachFrames(STAGE_FRAME_KEY);stageFramePosition(activeIndexes);setStageShaftMinmaxAndSnap();setNavShaftMinMax();addEnterUp($stageShaft[0],function(){if(!$fotorama.hasClass(fullscreenClass)){that.requestFullScreen();$fullscreenIcon.focus()}})}}function extendMeasures(options,measuresArray){if(!options)return;$.each(measuresArray,function(i,measures){if(!measures)return;$.extend(measures,{width:options.width||measures.width,height:options.height,minwidth:options.minwidth,maxwidth:options.maxwidth,minheight:options.minheight,maxheight:options.maxheight,ratio:getRatio(options.ratio)})})}function triggerEvent(event,extra){$fotorama.trigger(_fotoramaClass+":"+event,[that,extra])}function onTouchStart(){clearTimeout(onTouchEnd.t);touchedFLAG=1;if(opts.stopautoplayontouch){that.stopAutoplay()}else{pausedAutoplayFLAG=true}}function onTouchEnd(){if(!touchedFLAG)return;if(!opts.stopautoplayontouch){releaseAutoplay();changeAutoplay()}onTouchEnd.t=setTimeout(function(){touchedFLAG=0},TRANSITION_DURATION+TOUCH_TIMEOUT)}function releaseAutoplay(){pausedAutoplayFLAG=!!($videoPlaying||stoppedAutoplayFLAG)}function changeAutoplay(){clearTimeout(changeAutoplay.t);waitFor.stop(changeAutoplay.w);if(!opts.autoplay||pausedAutoplayFLAG){if(that.autoplay){that.autoplay=false;triggerEvent("stopautoplay")}return}if(!that.autoplay){that.autoplay=true;triggerEvent("startautoplay")}var _activeIndex=activeIndex;var frameData=activeFrame[STAGE_FRAME_KEY].data();changeAutoplay.w=waitFor(function(){return frameData.state||_activeIndex!==activeIndex},function(){changeAutoplay.t=setTimeout(function(){if(pausedAutoplayFLAG||_activeIndex!==activeIndex)return;var _nextAutoplayIndex=nextAutoplayIndex,nextFrameData=data[_nextAutoplayIndex][STAGE_FRAME_KEY].data();changeAutoplay.w=waitFor(function(){return nextFrameData.state||_nextAutoplayIndex!==nextAutoplayIndex},function(){if(pausedAutoplayFLAG||_nextAutoplayIndex!==nextAutoplayIndex)return;that.show(o_loop?getDirectionSign(!o_rtl):nextAutoplayIndex)})},opts.autoplay)})}that.startAutoplay=function(interval){if(that.autoplay)return this;pausedAutoplayFLAG=stoppedAutoplayFLAG=false;setAutoplayInterval(interval||opts.autoplay);changeAutoplay();return this};that.stopAutoplay=function(){if(that.autoplay){pausedAutoplayFLAG=stoppedAutoplayFLAG=true;changeAutoplay()}return this};that.showSlide=function(slideDir){var currentPosition=readPosition($navShaft,opts.navdir),pos,time=500*1.1,size=opts.navdir==="horizontal"?opts.thumbwidth:opts.thumbheight,onEnd=function(){thumbArrUpdate()};if(slideDir==="next"){pos=currentPosition-(size+opts.margin)*thumbsPerSlide}if(slideDir==="prev"){pos=currentPosition+(size+opts.margin)*thumbsPerSlide}pos=validateRestrictions(pos,navShaftTouchTail);thumbsDraw(pos,true);slide($navShaft,{time:time,pos:pos,direction:opts.navdir,onEnd:onEnd})};that.showWhileLongPress=function(options){if(that.longPress.singlePressInProgress){return}var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options)/50;var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showNav(silent,options,time);return this};that.showEndLongPress=function(options){if(that.longPress.singlePressInProgress){return}var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options)/50;var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showStage(silent,options,time);showedFLAG=typeof lastActiveIndex!=="undefined"&&lastActiveIndex!==activeIndex;lastActiveIndex=activeIndex;return this};function calcActiveIndex(options){var index;if(typeof options!=="object"){index=options;options={}}else{index=options.index}index=index===">"?dirtyIndex+1:index==="<"?dirtyIndex-1:index==="<<"?0:index===">>"?size-1:index;index=isNaN(index)?undefined:index;index=typeof index==="undefined"?activeIndex||0:index;return index}function calcGlobalIndexes(index){that.activeIndex=activeIndex=edgeIndex(index);prevIndex=getPrevIndex(activeIndex);nextIndex=getNextIndex(activeIndex);nextAutoplayIndex=normalizeIndex(activeIndex+(o_rtl?-1:1));activeIndexes=[activeIndex,prevIndex,nextIndex];dirtyIndex=o_loop?index:activeIndex}function calcTime(options){var diffIndex=Math.abs(lastActiveIndex-dirtyIndex),time=getNumber(options.time,function(){return Math.min(o_transitionDuration*(1+(diffIndex-1)/12),o_transitionDuration*2)});if(options.slow){time*=10}return time}that.showStage=function(silent,options,time){unloadVideo($videoPlaying,activeFrame.i!==data[normalizeIndex(repositionIndex)].i);frameDraw(activeIndexes,"stage");stageFramePosition(SLOW?[dirtyIndex]:[dirtyIndex,getPrevIndex(dirtyIndex),getNextIndex(dirtyIndex)]);updateTouchTails("go",true);silent||triggerEvent("show",{user:options.user,time:time});pausedAutoplayFLAG=true;var overPos=options.overPos;var onEnd=that.showStage.onEnd=function(skipReposition){if(onEnd.ok)return;onEnd.ok=true;skipReposition||stageShaftReposition(true);if(!silent){triggerEvent("showend",{user:options.user})}if(!skipReposition&&o_transition&&o_transition!==opts.transition){that.setOptions({transition:o_transition});o_transition=false;return}updateFotoramaState();loadImg(activeIndexes,"stage");updateTouchTails("go",false);stageWheelUpdate();stageCursor();releaseAutoplay();changeAutoplay();if(that.fullScreen){activeFrame[STAGE_FRAME_KEY].find("."+imgFullClass).attr("aria-hidden",false);activeFrame[STAGE_FRAME_KEY].find("."+imgClass).attr("aria-hidden",true)}else{activeFrame[STAGE_FRAME_KEY].find("."+imgFullClass).attr("aria-hidden",true);activeFrame[STAGE_FRAME_KEY].find("."+imgClass).attr("aria-hidden",false)}};if(!o_fade){slide($stageShaft,{pos:-getPosByIndex(dirtyIndex,measures.w,opts.margin,repositionIndex),overPos:overPos,time:time,onEnd:onEnd})}else{var $activeFrame=activeFrame[STAGE_FRAME_KEY],$prevActiveFrame=data[lastActiveIndex]&&activeIndex!==lastActiveIndex?data[lastActiveIndex][STAGE_FRAME_KEY]:null;fade($activeFrame,$prevActiveFrame,$stageFrame,{time:time,method:opts.transition,onEnd:onEnd},fadeStack)}arrsUpdate()};that.showNav=function(silent,options,time){thumbArrUpdate();if(o_nav){navUpdate();var guessIndex=limitIndex(activeIndex+minMaxLimit(dirtyIndex-lastActiveIndex,-1,1));slideNavShaft({time:time,coo:guessIndex!==activeIndex&&options.coo,guessIndex:typeof options.coo!=="undefined"?guessIndex:activeIndex,keep:silent});if(o_navThumbs)slideThumbBorder(time)}};that.show=function(options){that.longPress.singlePressInProgress=true;var index=calcActiveIndex(options);calcGlobalIndexes(index);var time=calcTime(options);var _activeFrame=activeFrame;that.activeFrame=activeFrame=data[activeIndex];var silent=_activeFrame===activeFrame&&!options.user;that.showStage(silent,options,time);that.showNav(silent,options,time);showedFLAG=typeof lastActiveIndex!=="undefined"&&lastActiveIndex!==activeIndex;lastActiveIndex=activeIndex;that.longPress.singlePressInProgress=false;return this};that.requestFullScreen=function(){if(o_allowFullScreen&&!that.fullScreen){var isVideo=$((that.activeFrame||{}).$stageFrame||{}).hasClass("fotorama-video-container");if(isVideo){return}scrollTop=$WINDOW.scrollTop();scrollLeft=$WINDOW.scrollLeft();lockScroll($WINDOW);updateTouchTails("x",true);measuresStash=$.extend({},measures);$fotorama.addClass(fullscreenClass).appendTo($BODY.addClass(_fullscreenClass));$HTML.addClass(_fullscreenClass);unloadVideo($videoPlaying,true,true);that.fullScreen=true;if(o_nativeFullScreen){fullScreenApi.request(fotorama)}that.resize();loadImg(activeIndexes,"stage");updateFotoramaState();triggerEvent("fullscreenenter");if(!("ontouchstart"in window)){$fullscreenIcon.focus()}}return this};function cancelFullScreen(){if(that.fullScreen){that.fullScreen=false;if(FULLSCREEN){fullScreenApi.cancel(fotorama)}$BODY.removeClass(_fullscreenClass);$HTML.removeClass(_fullscreenClass);$fotorama.removeClass(fullscreenClass).insertAfter($anchor);measures=$.extend({},measuresStash);unloadVideo($videoPlaying,true,true);updateTouchTails("x",false);that.resize();loadImg(activeIndexes,"stage");lockScroll($WINDOW,scrollLeft,scrollTop);triggerEvent("fullscreenexit")}}that.cancelFullScreen=function(){if(o_nativeFullScreen&&fullScreenApi.is()){fullScreenApi.cancel(document)}else{cancelFullScreen()}return this};that.toggleFullScreen=function(){return that[(that.fullScreen?"cancel":"request")+"FullScreen"]()};that.resize=function(options){if(!data)return this;var time=arguments[1]||0,setFLAG=arguments[2];thumbsPerSlide=getThumbsInSlide($wrap,opts);extendMeasures(!that.fullScreen?optionsToLowerCase(options):{width:$(window).width(),maxwidth:null,minwidth:null,height:$(window).height(),maxheight:null,minheight:null},[measures,setFLAG||that.fullScreen||opts]);var width=measures.width,height=measures.height,ratio=measures.ratio,windowHeight=$WINDOW.height()-(o_nav?$nav.height():0);if(measureIsValid(width)){$wrap.css({width:""});$wrap.css({height:""});$stage.css({width:""});$stage.css({height:""});$stageShaft.css({width:""});$stageShaft.css({height:""});$nav.css({width:""});$nav.css({height:""});$wrap.css({minWidth:measures.minwidth||0,maxWidth:measures.maxwidth||MAX_WIDTH});if(o_nav==="dots"){$navWrap.hide()}width=measures.W=measures.w=$wrap.width();measures.nw=o_nav&&numberFromWhatever(opts.navwidth,width)||width;$stageShaft.css({width:measures.w,marginLeft:(measures.W-measures.w)/2});height=numberFromWhatever(height,windowHeight);height=height||ratio&&width/ratio;if(height){width=Math.round(width);height=measures.h=Math.round(minMaxLimit(height,numberFromWhatever(measures.minheight,windowHeight),numberFromWhatever(measures.maxheight,windowHeight)));$stage.css({width:width,height:height});if(opts.navdir==="vertical"&&!that.fullscreen){$nav.width(opts.thumbwidth+opts.thumbmargin*2)}if(opts.navdir==="horizontal"&&!that.fullscreen){$nav.height(opts.thumbheight+opts.thumbmargin*2)}if(o_nav==="dots"){$nav.width(width).height("auto");$navWrap.show()}if(opts.navdir==="vertical"&&that.fullScreen){$stage.css("height",$WINDOW.height())}if(opts.navdir==="horizontal"&&that.fullScreen){$stage.css("height",$WINDOW.height()-$nav.height())}if(o_nav){switch(opts.navdir){case"vertical":$navWrap.removeClass(navShafthorizontalClass);$navWrap.removeClass(navShaftListClass);$navWrap.addClass(navShaftVerticalClass);$nav.stop().animate({height:measures.h,width:opts.thumbwidth},time);break;case"list":$navWrap.removeClass(navShaftVerticalClass);$navWrap.removeClass(navShafthorizontalClass);$navWrap.addClass(navShaftListClass);break;default:$navWrap.removeClass(navShaftVerticalClass);$navWrap.removeClass(navShaftListClass);$navWrap.addClass(navShafthorizontalClass);$nav.stop().animate({width:measures.nw},time);break}stageShaftReposition();slideNavShaft({guessIndex:activeIndex,time:time,keep:true});if(o_navThumbs&&frameAppend.nav)slideThumbBorder(time)}measuresSetFLAG=setFLAG||true;ready.ok=true;ready()}}stageLeft=$stage.offset().left;setStagePosition();return this};that.setOptions=function(options){$.extend(opts,options);reset();return this};that.shuffle=function(){data&&shuffle(data)&&reset();return this};function setShadow($el,edge){if(o_shadows){$el.removeClass(shadowsLeftClass+" "+shadowsRightClass);$el.removeClass(shadowsTopClass+" "+shadowsBottomClass);edge&&!$videoPlaying&&$el.addClass(edge.replace(/^|\s/g," "+shadowsClass+"--"))}}that.longPress={threshold:1,count:0,thumbSlideTime:20,progress:function(){if(!this.inProgress){this.count++;this.inProgress=this.count>this.threshold}},end:function(){if(this.inProgress){this.isEnded=true}},reset:function(){this.count=0;this.inProgress=false;this.isEnded=false}};that.destroy=function(){that.cancelFullScreen();that.stopAutoplay();data=that.data=null;appendElements();activeIndexes=[];detachFrames(STAGE_FRAME_KEY);reset.ok=false;return this};that.playVideo=function(){var dataFrame=activeFrame,video=dataFrame.video,_activeIndex=activeIndex;if(typeof video==="object"&&dataFrame.videoReady){o_nativeFullScreen&&that.fullScreen&&that.cancelFullScreen();waitFor(function(){return!fullScreenApi.is()||_activeIndex!==activeIndex},function(){if(_activeIndex===activeIndex){dataFrame.$video=dataFrame.$video||$(div(videoClass)).append(createVideoFrame(video));dataFrame.$video.appendTo(dataFrame[STAGE_FRAME_KEY]);$wrap.addClass(wrapVideoClass);$videoPlaying=dataFrame.$video;stageNoMove();$arrs.blur();$fullscreenIcon.blur();triggerEvent("loadvideo")}})}return this};that.stopVideo=function(){unloadVideo($videoPlaying,true,true);return this};that.spliceByIndex=function(index,newImgObj){newImgObj.i=index+1;newImgObj.img&&$.ajax({url:newImgObj.img,type:"HEAD",success:function(){data.splice(index,1,newImgObj);reset()}})};function unloadVideo($video,unloadActiveFLAG,releaseAutoplayFLAG){if(unloadActiveFLAG){$wrap.removeClass(wrapVideoClass);$videoPlaying=false;stageNoMove()}if($video&&$video!==$videoPlaying){$video.remove();triggerEvent("unloadvideo")}if(releaseAutoplayFLAG){releaseAutoplay();changeAutoplay()}}function toggleControlsClass(FLAG){$wrap.toggleClass(wrapNoControlsClass,FLAG)}function stageCursor(e){if(stageShaftTouchTail.flow)return;var x=e?e.pageX:stageCursor.x,pointerFLAG=x&&!disableDirrection(getDirection(x))&&opts.click;if(stageCursor.p!==pointerFLAG&&$stage.toggleClass(pointerClass,pointerFLAG)){stageCursor.p=pointerFLAG;stageCursor.x=x}}$stage.on("mousemove",stageCursor);function clickToShow(showOptions){clearTimeout(clickToShow.t);if(opts.clicktransition&&opts.clicktransition!==opts.transition){setTimeout(function(){var _o_transition=opts.transition;that.setOptions({transition:opts.clicktransition});o_transition=_o_transition;clickToShow.t=setTimeout(function(){that.show(showOptions)},10)},0)}else{that.show(showOptions)}}function onStageTap(e,toggleControlsFLAG){var target=e.target,$target=$(target);if($target.hasClass(videoPlayClass)){that.playVideo()}else if(target===fullscreenIcon){that.toggleFullScreen()}else if($videoPlaying){target===videoClose&&unloadVideo($videoPlaying,true,true)}else if(!$fotorama.hasClass(fullscreenClass)){that.requestFullScreen()}}function updateTouchTails(key,value){stageShaftTouchTail[key]=navShaftTouchTail[key]=value}stageShaftTouchTail=moveOnTouch($stageShaft,{onStart:onTouchStart,onMove:function(e,result){setShadow($stage,result.edge)},onTouchEnd:onTouchEnd,onEnd:function(result){var toggleControlsFLAG;setShadow($stage);toggleControlsFLAG=(MS_POINTER&&!hoverFLAG||result.touch)&&opts.arrows;if((result.moved||toggleControlsFLAG&&result.pos!==result.newPos&&!result.control)&&result.$target[0]!==$fullscreenIcon[0]){var index=getIndexByPos(result.newPos,measures.w,opts.margin,repositionIndex);that.show({index:index,time:o_fade?o_transitionDuration:result.time,overPos:result.overPos,user:true})}else if(!result.aborted&&!result.control){onStageTap(result.startEvent,toggleControlsFLAG)}},timeLow:1,timeHigh:1,friction:2,select:"."+selectClass+", ."+selectClass+" *",$wrap:$stage,direction:"horizontal"});navShaftTouchTail=moveOnTouch($navShaft,{onStart:onTouchStart,onMove:function(e,result){setShadow($nav,result.edge)},onTouchEnd:onTouchEnd,onEnd:function(result){function onEnd(){slideNavShaft.l=result.newPos;releaseAutoplay();changeAutoplay();thumbsDraw(result.newPos,true);thumbArrUpdate()}if(!result.moved){var target=result.$target.closest("."+navFrameClass,$navShaft)[0];target&&onNavFrameClick.call(target,result.startEvent)}else if(result.pos!==result.newPos){pausedAutoplayFLAG=true;slide($navShaft,{time:result.time,pos:result.newPos,overPos:result.overPos,direction:opts.navdir,onEnd:onEnd});thumbsDraw(result.newPos);o_shadows&&setShadow($nav,findShadowEdge(result.newPos,navShaftTouchTail.min,navShaftTouchTail.max,result.dir))}else{onEnd()}},timeLow:.5,timeHigh:2,friction:5,$wrap:$nav,direction:opts.navdir});stageWheelTail=wheel($stage,{shift:true,onEnd:function(e,direction){onTouchStart();onTouchEnd();that.show({index:direction,slow:e.altKey})}});navWheelTail=wheel($nav,{onEnd:function(e,direction){onTouchStart();onTouchEnd();var newPos=stop($navShaft)+direction*.25;$navShaft.css(getTranslate(minMaxLimit(newPos,navShaftTouchTail.min,navShaftTouchTail.max),opts.navdir));o_shadows&&setShadow($nav,findShadowEdge(newPos,navShaftTouchTail.min,navShaftTouchTail.max,opts.navdir));navWheelTail.prevent={"<":newPos>=navShaftTouchTail.max,">":newPos<=navShaftTouchTail.min};clearTimeout(navWheelTail.t);navWheelTail.t=setTimeout(function(){slideNavShaft.l=newPos;thumbsDraw(newPos,true)},TOUCH_TIMEOUT);thumbsDraw(newPos)}});$wrap.hover(function(){setTimeout(function(){if(touchedFLAG)return;toggleControlsClass(!(hoverFLAG=true))},0)},function(){if(!hoverFLAG)return;toggleControlsClass(!(hoverFLAG=false))});function onNavFrameClick(e){var index=$(this).data().eq;if(opts.navtype==="thumbs"){clickToShow({index:index,slow:e.altKey,user:true,coo:e._x-$nav.offset().left})}else{clickToShow({index:index,slow:e.altKey,user:true})}}function onArrClick(e){clickToShow({index:$arrs.index(this)?">":"<",slow:e.altKey,user:true})}smartClick($arrs,function(e){stopEvent(e);onArrClick.call(this,e)},{onStart:function(){onTouchStart();stageShaftTouchTail.control=true},onTouchEnd:onTouchEnd});smartClick($thumbArrLeft,function(e){stopEvent(e);if(opts.navtype==="thumbs"){that.show("<")}else{that.showSlide("prev")}});smartClick($thumbArrRight,function(e){stopEvent(e);if(opts.navtype==="thumbs"){that.show(">")}else{that.showSlide("next")}});function addFocusOnControls(el){addFocus(el,function(){setTimeout(function(){lockScroll($stage)},0);toggleControlsClass(false)})}$arrs.each(function(){addEnterUp(this,function(e){onArrClick.call(this,e)});addFocusOnControls(this)});addEnterUp(fullscreenIcon,function(){if($fotorama.hasClass(fullscreenClass)){that.cancelFullScreen();$stageShaft.focus()}else{that.requestFullScreen();$fullscreenIcon.focus()}});addFocusOnControls(fullscreenIcon);function reset(){setData();setOptions();if(!reset.i){reset.i=true;var _startindex=opts.startindex;activeIndex=repositionIndex=dirtyIndex=lastActiveIndex=startIndex=edgeIndex(_startindex)||0}if(size){if(changeToRtl())return;if($videoPlaying){unloadVideo($videoPlaying,true)}activeIndexes=[];detachFrames(STAGE_FRAME_KEY);reset.ok=true;that.show({index:activeIndex,time:0});that.resize()}else{that.destroy()}}function changeToRtl(){if(!changeToRtl.f===o_rtl){changeToRtl.f=o_rtl;activeIndex=size-1-activeIndex;that.reverse();return true}}$.each("load push pop shift unshift reverse sort splice".split(" "),function(i,method){that[method]=function(){data=data||[];if(method!=="load"){Array.prototype[method].apply(data,arguments)}else if(arguments[0]&&typeof arguments[0]==="object"&&arguments[0].length){data=clone(arguments[0])}reset();return that}});function ready(){if(ready.ok){ready.ok=false;triggerEvent("ready")}}reset()};$.fn.fotorama=function(opts){return this.each(function(){var that=this,$fotorama=$(this),fotoramaData=$fotorama.data(),fotorama=fotoramaData.fotorama;if(!fotorama){waitFor(function(){return!isHidden(that)},function(){fotoramaData.urtext=$fotorama.html();new $.Fotorama($fotorama,$.extend({},OPTIONS,window.fotoramaDefaults,opts,fotoramaData))})}else{fotorama.setOptions(opts,true)}})};$.Fotorama.instances=[];function calculateIndexes(){$.each($.Fotorama.instances,function(index,instance){instance.index=index})}function addInstance(instance){$.Fotorama.instances.push(instance);calculateIndexes()}function hideInstance(instance){$.Fotorama.instances.splice(instance.index,1);calculateIndexes()}$.Fotorama.cache={};$.Fotorama.measures={};$=$||{};$.Fotorama=$.Fotorama||{};$.Fotorama.jst=$.Fotorama.jst||{};$.Fotorama.jst.dots=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__nav__frame fotorama__nav__frame--dot" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__dot"></div>\r\n</div>';return __p};$.Fotorama.jst.frameCaption=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__caption" aria-hidden="true">\r\n <div class="fotorama__caption__wrap" id="'+((__t=v.labelledby)==null?"":__t)+'">'+((__t=v.caption)==null?"":__t)+"</div>\r\n</div>\r\n";return __p};$.Fotorama.jst.style=function(v){var __t,__p="",__e=_.escape;__p+=".fotorama"+((__t=v.s)==null?"":__t)+" .fotorama__nav--thumbs .fotorama__nav__frame{\r\npadding:"+((__t=v.m)==null?"":__t)+"px;\r\nheight:"+((__t=v.h)==null?"":__t)+"px}\r\n.fotorama"+((__t=v.s)==null?"":__t)+" .fotorama__thumb-border{\r\nheight:"+((__t=v.h)==null?"":__t)+"px;\r\nborder-width:"+((__t=v.b)==null?"":__t)+"px;\r\nmargin-top:"+((__t=v.m)==null?"":__t)+"px}";return __p};$.Fotorama.jst.thumb=function(v){var __t,__p="",__e=_.escape;__p+='<div class="fotorama__nav__frame fotorama__nav__frame--thumb" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__thumb">\r\n </div>\r\n</div>';return __p}})(window,document,location,typeof jQuery!=="undefined"&&jQuery); diff --git a/lib/web/images/logo.svg b/lib/web/images/logo.svg index 013d6e7c5a1..0f29d4e3eef 100644 --- a/lib/web/images/logo.svg +++ b/lib/web/images/logo.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="55.139px" viewBox="0 0 189 55.139" width="189px" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 189 55.139"><path d="m23.333 0l-23.333 14.135v26.865l6.06 3.568v-26.865l17.278-10.504 17.292 10.488 0.074 0.042-0.008 26.798 6.001-3.527v-26.865l-23.364-14.135zm3.088 16.538v31.407l-3.088 1.889-3.091-1.896v-31.376l-8.003 4.928v26.86l11.094 6.789 11.189-6.837v-26.829l-8.101-4.935z" fill="#ED7402"/><path d="m12.239 21.491l8.003 4.886v-9.814l-8.003 4.928zm14.182-4.953v9.902l8.101-4.967-8.101-4.935zm20.276-2.403l-23.364-14.135-23.333 14.135 6.06 3.568 17.278-10.504 17.365 10.53 5.994-3.594z" fill="#F8B97F"/><path d="m82.328 39.984l-1.608-20.54-8.156 20.651h-2.656l-8.156-20.651-1.571 20.541h-3.293l2.058-25.814h4.34l8.043 21.174 8.044-21.174h4.301l2.021 25.814h-3.367z" fill="#131108"/><path d="m99.91 39.984l-0.375-2.396c-1.421 1.458-3.366 2.769-6.284 2.769-3.218 0-5.237-1.945-5.237-4.977 0-4.452 3.815-6.209 11.261-6.996v-0.748c0-2.244-1.348-3.03-3.406-3.03-2.17 0-4.227 0.673-6.172 1.533l-0.449-2.88c2.133-0.861 4.153-1.496 6.922-1.496 4.339 0 6.436 1.757 6.436 5.723v12.498h-2.7zm-0.635-9.055c-6.586 0.636-7.97 2.432-7.97 4.266 0 1.457 0.973 2.394 2.657 2.394 1.945 0 3.815-0.974 5.312-2.508v-4.152h0.001z" fill="#131108"/><path d="m121.72 21.876l0.485 2.992-3.404 0.336c0.486 0.824 0.712 1.76 0.712 2.769 0 3.817-3.219 6.134-6.848 6.134-0.449 0-0.898-0.037-1.348-0.111-0.523 0.338-0.897 0.75-0.897 1.085 0 0.636 0.634 0.787 3.777 1.349l1.272 0.223c3.778 0.673 6.135 1.871 6.135 4.639 0 3.741-4.078 5.5-8.715 5.5-4.64 0-8.343-1.458-8.343-4.6 0-1.834 1.271-3.256 3.777-4.603-0.784-0.562-1.121-1.198-1.121-1.872 0-0.861 0.672-1.722 1.87-2.432-1.982-0.973-3.33-2.879-3.33-5.312 0-3.852 3.217-6.208 6.846-6.208 1.795 0 3.368 0.522 4.604 1.496l4.53-1.385zm-13.99 20.052c0 1.424 1.834 2.471 5.312 2.471 3.48 0 5.424-1.197 5.424-2.693 0-1.086-0.822-1.832-3.365-2.282l-2.134-0.375c-0.972-0.187-1.495-0.299-2.207-0.448-2.1 1.045-3.03 2.094-3.03 3.327zm4.86-17.733c-2.244 0-3.629 1.722-3.629 3.891 0 2.058 1.421 3.665 3.629 3.665 2.283 0 3.704-1.682 3.704-3.815 0.01-2.132-1.49-3.741-3.7-3.741z" fill="#131108"/><path d="m137.58 31.416h-12.122c0.112 4.15 2.094 6.098 5.2 6.098 2.583 0 4.453-1.009 6.397-2.544l0.484 2.994c-1.907 1.497-4.188 2.394-7.143 2.394-4.642 0-8.271-2.807-8.271-9.354 0-5.724 3.368-9.239 7.856-9.239 5.199 0 7.595 4.002 7.595 8.94v0.711zm-7.63-7.033c-2.059 0-3.817 1.459-4.34 4.526h8.604c-0.41-2.881-1.68-4.526-4.26-4.526z" fill="#131108"/><path d="m151.61 39.984v-12.16c0-1.832-0.786-3.067-2.73-3.067-1.759 0-3.555 1.161-5.163 2.88v12.347h-3.329v-17.846h2.655l0.412 2.582c1.683-1.533 3.78-2.955 6.321-2.955 3.369 0 5.166 2.019 5.166 5.236v12.983h-3.33z" fill="#131108"/><path d="m164.78 40.284c-3.143 0-5.199-1.124-5.199-4.716v-10.624h-2.694v-2.806h2.694v-5.949l3.255-0.485v6.434h3.853l0.449 2.806h-4.302v10.025c0 1.461 0.599 2.357 2.47 2.357 0.598 0 1.121-0.035 1.531-0.112l0.45 2.843c-0.57 0.112-1.35 0.227-2.51 0.227z" fill="#131108"/><path d="m175.82 40.357c-4.752 0-8.194-3.403-8.194-9.278 0-5.874 3.442-9.314 8.194-9.314 4.787 0 8.305 3.44 8.305 9.314 0 5.875-3.52 9.278-8.3 9.278zm0-15.788c-3.217 0-4.826 2.769-4.826 6.51 0 3.668 1.683 6.511 4.826 6.511 3.291 0 4.938-2.77 4.938-6.511-0.01-3.666-1.73-6.51-4.94-6.51z" fill="#131108"/><path d="m186.48 24.81c-1.338 0-2.268-0.929-2.268-2.318 0-1.379 0.95-2.328 2.268-2.328 1.34 0 2.27 0.939 2.27 2.328 0 1.378-0.95 2.318-2.27 2.318zm0-4.376c-1.078 0-1.938 0.739-1.938 2.058 0 1.31 0.859 2.049 1.938 2.049 1.09 0 1.949-0.739 1.949-2.049 0-1.319-0.87-2.058-1.95-2.058zm0.67 3.297l-0.768-1.099h-0.249v1.059h-0.44v-2.568h0.779c0.54 0 0.898 0.27 0.898 0.75 0 0.37-0.201 0.609-0.52 0.709l0.74 1.049-0.44 0.1zm-0.68-2.209h-0.34v0.759h0.319c0.29 0 0.471-0.12 0.471-0.379 0-0.25-0.16-0.38-0.45-0.38z" fill="#131108"/></svg> \ No newline at end of file +<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.073 50"><style>.st0{fill:#f26322}.st1{fill:#4d4d4d}</style><path class="st0" d="M21.432 0L0 12.373v24.713l6.117 3.533-.041-24.713 15.315-8.842 15.313 8.842v24.706l6.118-3.526V12.35z"/><path class="st0" d="M24.47 40.618l-3.058 1.772-3.071-1.759V15.906l-6.116 3.532.01 24.712 9.172 5.298 9.18-5.298V19.438l-6.117-3.532z"/><path class="st1" d="M56.838 12.522l8.415 21.258h.068l8.21-21.258h3.203V36.88h-2.215V15.656h-.068c-.114.386-.239.772-.374 1.158-.115.318-.246.67-.393 1.055-.147.387-.278.75-.391 1.09l-7.052 17.919h-2.01L57.11 18.96a19.913 19.913 0 0 1-.408-1.039 43.558 43.558 0 0 1-.375-1.073 68.067 68.067 0 0 0-.408-1.192h-.069v21.223h-2.112V12.522h3.1zM83.17 36.982a5.205 5.205 0 0 1-1.823-.92 4.327 4.327 0 0 1-1.209-1.533 4.881 4.881 0 0 1-.443-2.145 5.018 5.018 0 0 1 .579-2.556 4.472 4.472 0 0 1 1.568-1.584 7.927 7.927 0 0 1 2.299-.903 24.732 24.732 0 0 1 2.811-.477 36 36 0 0 0 2.198-.29 6.689 6.689 0 0 0 1.465-.392c.329-.123.614-.343.817-.63.187-.325.276-.698.256-1.073v-.34a3.212 3.212 0 0 0-1.09-2.674 4.93 4.93 0 0 0-3.134-.87c-3.135 0-4.781 1.306-4.941 3.918h-2.077a5.748 5.748 0 0 1 1.891-4.089 7.5 7.5 0 0 1 5.127-1.533 7.335 7.335 0 0 1 4.564 1.278 4.923 4.923 0 0 1 1.67 4.173v9.573c-.034.402.069.804.29 1.141.219.251.536.394.868.392.12-.001.24-.012.358-.034.124-.022.266-.057.425-.102h.103v1.533c-.188.077-.382.14-.58.188-.28.062-.566.091-.852.086a2.694 2.694 0 0 1-1.839-.597 2.575 2.575 0 0 1-.75-1.891v-.374h-.102c-.277.372-.578.725-.903 1.056-.38.385-.81.717-1.278.988a7.14 7.14 0 0 1-1.737.715 8.443 8.443 0 0 1-2.249.272 8.186 8.186 0 0 1-2.282-.306m5.195-1.857a5.971 5.971 0 0 0 1.857-1.175 4.767 4.767 0 0 0 1.499-3.441V27.34a7.295 7.295 0 0 1-2.061.732 33.21 33.21 0 0 1-2.504.426c-.749.114-1.441.233-2.077.358a5.237 5.237 0 0 0-1.652.596 3.055 3.055 0 0 0-1.108 1.107 3.579 3.579 0 0 0-.408 1.824c-.018.53.093 1.056.324 1.533.201.392.493.73.851.987a3.35 3.35 0 0 0 1.244.529c.493.104.995.155 1.499.153a6.555 6.555 0 0 0 2.536-.46M99.35 41.734a4.687 4.687 0 0 1-2.009-3.287h2.043c.14.966.763 1.794 1.652 2.197a7.522 7.522 0 0 0 3.288.664 5.688 5.688 0 0 0 4.173-1.345 4.997 4.997 0 0 0 1.345-3.697v-2.793h-.102a7.293 7.293 0 0 1-2.283 2.282 6.297 6.297 0 0 1-3.304.784 7.375 7.375 0 0 1-3.135-.647 6.921 6.921 0 0 1-2.385-1.806 8.095 8.095 0 0 1-1.516-2.777 11.429 11.429 0 0 1-.528-3.56 10.89 10.89 0 0 1 .613-3.799 8.483 8.483 0 0 1 1.636-2.777 6.75 6.75 0 0 1 2.402-1.703 7.45 7.45 0 0 1 2.913-.58 6.234 6.234 0 0 1 3.372.835 6.938 6.938 0 0 1 2.215 2.265h.102v-2.725h2.078v16.931a6.78 6.78 0 0 1-1.636 4.735 7.751 7.751 0 0 1-5.893 2.112 8.337 8.337 0 0 1-5.041-1.309m9.232-8.908a8.565 8.565 0 0 0 1.397-5.11 11.22 11.22 0 0 0-.34-2.862 6.239 6.239 0 0 0-1.056-2.231 4.828 4.828 0 0 0-1.789-1.448 5.772 5.772 0 0 0-2.503-.511 4.782 4.782 0 0 0-4.071 1.941 8.464 8.464 0 0 0-1.448 5.179c-.008.936.107 1.87.34 2.777a6.64 6.64 0 0 0 1.022 2.214 4.81 4.81 0 0 0 1.703 1.465 5.208 5.208 0 0 0 2.42.528 4.967 4.967 0 0 0 4.325-1.942M119.244 36.624a7.19 7.19 0 0 1-2.572-1.941 8.66 8.66 0 0 1-1.583-2.93 11.839 11.839 0 0 1-.546-3.662 11.179 11.179 0 0 1 .58-3.663 9.138 9.138 0 0 1 1.617-2.929 7.307 7.307 0 0 1 2.522-1.942 7.684 7.684 0 0 1 3.321-.698 7.275 7.275 0 0 1 3.56.8 6.678 6.678 0 0 1 2.351 2.146 8.806 8.806 0 0 1 1.278 3.083 16.87 16.87 0 0 1 .374 3.577h-13.422c.013.941.157 1.875.426 2.777a6.968 6.968 0 0 0 1.124 2.231 5.108 5.108 0 0 0 1.857 1.499 5.948 5.948 0 0 0 2.623.546 4.985 4.985 0 0 0 3.424-1.074 5.875 5.875 0 0 0 1.719-2.878h2.044a7.51 7.51 0 0 1-2.385 4.19 7.072 7.072 0 0 1-4.803 1.567 8.386 8.386 0 0 1-3.509-.699m8.312-12.264a5.986 5.986 0 0 0-.988-1.976 4.525 4.525 0 0 0-1.635-1.311 5.362 5.362 0 0 0-2.351-.478 5.623 5.623 0 0 0-2.368.478 5.064 5.064 0 0 0-1.754 1.311 6.566 6.566 0 0 0-1.141 1.96 9.615 9.615 0 0 0-.562 2.453h11.174a9.268 9.268 0 0 0-.375-2.437M134.879 19.267v2.691h.068a7.237 7.237 0 0 1 2.333-2.197 6.798 6.798 0 0 1 3.561-.868 5.834 5.834 0 0 1 4.037 1.413 5.174 5.174 0 0 1 1.584 4.071V36.88h-2.112V24.581a3.716 3.716 0 0 0-1.073-2.947 4.334 4.334 0 0 0-2.948-.937 5.896 5.896 0 0 0-2.111.375 5.558 5.558 0 0 0-1.738 1.039 4.717 4.717 0 0 0-1.601 3.593V36.88h-2.112V19.267h2.112zM151.912 36.284a2.934 2.934 0 0 1-.92-2.436V21.005h-2.657v-1.738h2.657v-5.416h2.112v5.416h3.271v1.738h-3.271v12.502a1.65 1.65 0 0 0 .426 1.312c.371.265.823.391 1.277.357.258-.001.515-.029.766-.085.215-.043.426-.106.63-.188h.103v1.806a5.907 5.907 0 0 1-1.942.306 3.819 3.819 0 0 1-2.452-.731M162.625 36.624a7.368 7.368 0 0 1-2.571-1.942 8.732 8.732 0 0 1-1.618-2.929 12.217 12.217 0 0 1 0-7.324 8.744 8.744 0 0 1 1.618-2.93 7.386 7.386 0 0 1 2.571-1.942 8.106 8.106 0 0 1 3.424-.698 7.989 7.989 0 0 1 3.406.698 7.424 7.424 0 0 1 2.556 1.942 8.504 8.504 0 0 1 1.601 2.93 12.57 12.57 0 0 1 0 7.324 8.5 8.5 0 0 1-1.601 2.929 7.415 7.415 0 0 1-2.556 1.942 7.959 7.959 0 0 1-3.406.698 8.075 8.075 0 0 1-3.424-.698m6.013-1.652a5.308 5.308 0 0 0 1.873-1.601 7.215 7.215 0 0 0 1.124-2.385 11.348 11.348 0 0 0 0-5.792 7.215 7.215 0 0 0-1.124-2.385 5.289 5.289 0 0 0-1.873-1.601 6.109 6.109 0 0 0-5.195 0 5.497 5.497 0 0 0-1.874 1.601 7.046 7.046 0 0 0-1.141 2.385 11.392 11.392 0 0 0 0 5.792c.227.86.614 1.669 1.141 2.385a5.817 5.817 0 0 0 7.069 1.601M176.856 22.191a2.128 2.128 0 0 1-2.213-2.265 2.216 2.216 0 1 1 4.431 0 2.146 2.146 0 0 1-2.218 2.265m0-4.277a1.845 1.845 0 0 0-1.892 2.012 1.9 1.9 0 1 0 3.797 0 1.854 1.854 0 0 0-1.905-2.012m.653 3.222l-.751-1.073h-.243v1.035h-.43v-2.509h.763c.526 0 .877.264.877.732a.681.681 0 0 1-.508.693l.724 1.025-.432.097zm-.661-2.157h-.333v.741h.312c.283 0 .46-.117.46-.371.001-.244-.158-.37-.439-.37"/></svg> diff --git a/lib/web/images/magento-logo.svg b/lib/web/images/magento-logo.svg index 0d5cc0e6233..0f29d4e3eef 100644 --- a/lib/web/images/magento-logo.svg +++ b/lib/web/images/magento-logo.svg @@ -1 +1 @@ -<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="214px" xml:space="preserve" height="62px" viewBox="0 0 214 62" baseProfile="tiny" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="m93.166 44.96l-1.809-23.096-9.17 23.221h-2.988l-9.17-23.221-1.767 23.096h-3.702l2.314-29.026h4.88l9.045 23.809 9.045-23.809h4.836l2.271 29.026h-3.785z" fill="#131108"/><path d="m112.94 44.96l-0.421-2.692c-1.597 1.639-3.785 3.112-7.066 3.112-3.619 0-5.889-2.188-5.889-5.596 0-5.006 4.29-6.981 12.663-7.867v-0.841c0-2.523-1.515-3.407-3.83-3.407-2.439 0-4.754 0.757-6.94 1.725l-0.505-3.238c2.398-0.969 4.67-1.682 7.783-1.682 4.88 0 7.236 1.976 7.236 6.435v14.051h-3.02zm-0.72-10.182c-7.406 0.715-8.963 2.735-8.963 4.796 0 1.642 1.095 2.693 2.989 2.693 2.187 0 4.291-1.095 5.974-2.82v-4.669z" fill="#131108"/><path d="m137.46 24.599l0.546 3.364-3.826 0.378c0.546 0.926 0.799 1.979 0.799 3.113 0 4.292-3.618 6.899-7.699 6.899-0.504 0-1.011-0.042-1.514-0.126-0.589 0.38-1.01 0.844-1.01 1.22 0 0.716 0.714 0.886 4.248 1.517l1.432 0.252c4.249 0.757 6.898 2.102 6.898 5.216 0 4.206-4.586 6.183-9.802 6.183s-9.381-1.64-9.381-5.173c0-2.062 1.431-3.66 4.248-5.174-0.882-0.631-1.26-1.348-1.26-2.104 0-0.969 0.756-1.936 2.103-2.734-2.229-1.095-3.744-3.238-3.744-5.974 0-4.332 3.616-6.981 7.697-6.981 2.019 0 3.786 0.587 5.175 1.682l5.08-1.558zm-15.73 22.547c0 1.599 2.06 2.775 5.972 2.775 3.913 0 6.099-1.345 6.099-3.027 0-1.222-0.924-2.061-3.784-2.566l-2.397-0.422c-1.095-0.208-1.682-0.336-2.481-0.502-2.36 1.177-3.41 2.356-3.41 3.742zm5.47-19.939c-2.522 0-4.081 1.936-4.081 4.375 0 2.313 1.6 4.12 4.081 4.12 2.566 0 4.165-1.892 4.165-4.29 0-2.397-1.68-4.205-4.16-4.205z" fill="#131108"/><path d="m155.3 35.325h-13.631c0.125 4.669 2.354 6.856 5.847 6.856 2.904 0 5.007-1.135 7.193-2.86l0.546 3.367c-2.144 1.682-4.709 2.691-8.031 2.691-5.219 0-9.299-3.155-9.299-10.519 0-6.435 3.787-10.388 8.835-10.388 5.846 0 8.54 4.5 8.54 10.052v0.801zm-8.58-7.908c-2.313 0-4.291 1.641-4.879 5.09h9.675c-0.47-3.239-1.9-5.09-4.8-5.09z" fill="#131108"/><path d="m171.07 44.96v-13.673c0-2.06-0.883-3.449-3.07-3.449-1.977 0-3.996 1.305-5.807 3.239v13.883h-3.743v-20.067h2.986l0.463 2.903c1.893-1.724 4.251-3.323 7.108-3.323 3.786 0 5.808 2.271 5.808 5.888v14.599h-3.75z" fill="#131108"/><path d="m185.88 45.298c-3.532 0-5.846-1.265-5.846-5.304v-11.946h-3.03v-3.156h3.03v-6.688l3.66-0.546v7.234h4.332l0.505 3.156h-4.837v11.273c0 1.643 0.675 2.651 2.776 2.651 0.673 0 1.262-0.041 1.724-0.127l0.506 3.196c-0.63 0.128-1.51 0.257-2.81 0.257z" fill="#131108"/><path d="m198.29 45.38c-5.342 0-9.213-3.827-9.213-10.434 0-6.605 3.871-10.473 9.213-10.473 5.383 0 9.339 3.868 9.339 10.473 0 6.607-3.96 10.434-9.34 10.434zm0-17.753c-3.617 0-5.426 3.113-5.426 7.319 0 4.125 1.892 7.321 5.426 7.321 3.702 0 5.553-3.114 5.553-7.321 0-4.122-1.93-7.319-5.55-7.319z" fill="#131108"/><path d="m210.28 27.897c-1.505 0-2.551-1.045-2.551-2.606 0-1.55 1.067-2.618 2.551-2.618 1.505 0 2.55 1.056 2.55 2.618 0 1.55-1.07 2.606-2.55 2.606zm0-4.92c-1.214 0-2.18 0.831-2.18 2.314 0 1.472 0.966 2.303 2.18 2.303 1.225 0 2.191-0.832 2.191-2.303 0-1.483-0.98-2.314-2.19-2.314zm0.75 3.708l-0.863-1.237h-0.281v1.191h-0.495v-2.888h0.878c0.606 0 1.01 0.303 1.01 0.843 0 0.416-0.225 0.686-0.585 0.798l0.833 1.18-0.5 0.113zm-0.76-2.484h-0.383v0.854h0.359c0.325 0 0.53-0.135 0.53-0.427 0-0.281-0.18-0.427-0.51-0.427z" fill="#131108"/><g fill="#E85D22"><path d="m26.845 8.857"/><polygon points="53.692 15.5 53.692 46.5 46.021 50.929 46.021 19.929 26.845 8.857 7.67 19.928 7.67 50.929 0 46.5 0 15.5 26.845 0"/><polygon points="26.847 62 15.341 55.356 15.341 24.357 23.011 19.928 23.011 50.929 26.845 53.257 30.682 50.929 30.682 19.929 38.353 24.357 38.353 55.356"/></g></svg> \ No newline at end of file +<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.073 50"><style>.st0{fill:#f26322}.st1{fill:#4d4d4d}</style><path class="st0" d="M21.432 0L0 12.373v24.713l6.117 3.533-.041-24.713 15.315-8.842 15.313 8.842v24.706l6.118-3.526V12.35z"/><path class="st0" d="M24.47 40.618l-3.058 1.772-3.071-1.759V15.906l-6.116 3.532.01 24.712 9.172 5.298 9.18-5.298V19.438l-6.117-3.532z"/><path class="st1" d="M56.838 12.522l8.415 21.258h.068l8.21-21.258h3.203V36.88h-2.215V15.656h-.068c-.114.386-.239.772-.374 1.158-.115.318-.246.67-.393 1.055-.147.387-.278.75-.391 1.09l-7.052 17.919h-2.01L57.11 18.96a19.913 19.913 0 0 1-.408-1.039 43.558 43.558 0 0 1-.375-1.073 68.067 68.067 0 0 0-.408-1.192h-.069v21.223h-2.112V12.522h3.1zM83.17 36.982a5.205 5.205 0 0 1-1.823-.92 4.327 4.327 0 0 1-1.209-1.533 4.881 4.881 0 0 1-.443-2.145 5.018 5.018 0 0 1 .579-2.556 4.472 4.472 0 0 1 1.568-1.584 7.927 7.927 0 0 1 2.299-.903 24.732 24.732 0 0 1 2.811-.477 36 36 0 0 0 2.198-.29 6.689 6.689 0 0 0 1.465-.392c.329-.123.614-.343.817-.63.187-.325.276-.698.256-1.073v-.34a3.212 3.212 0 0 0-1.09-2.674 4.93 4.93 0 0 0-3.134-.87c-3.135 0-4.781 1.306-4.941 3.918h-2.077a5.748 5.748 0 0 1 1.891-4.089 7.5 7.5 0 0 1 5.127-1.533 7.335 7.335 0 0 1 4.564 1.278 4.923 4.923 0 0 1 1.67 4.173v9.573c-.034.402.069.804.29 1.141.219.251.536.394.868.392.12-.001.24-.012.358-.034.124-.022.266-.057.425-.102h.103v1.533c-.188.077-.382.14-.58.188-.28.062-.566.091-.852.086a2.694 2.694 0 0 1-1.839-.597 2.575 2.575 0 0 1-.75-1.891v-.374h-.102c-.277.372-.578.725-.903 1.056-.38.385-.81.717-1.278.988a7.14 7.14 0 0 1-1.737.715 8.443 8.443 0 0 1-2.249.272 8.186 8.186 0 0 1-2.282-.306m5.195-1.857a5.971 5.971 0 0 0 1.857-1.175 4.767 4.767 0 0 0 1.499-3.441V27.34a7.295 7.295 0 0 1-2.061.732 33.21 33.21 0 0 1-2.504.426c-.749.114-1.441.233-2.077.358a5.237 5.237 0 0 0-1.652.596 3.055 3.055 0 0 0-1.108 1.107 3.579 3.579 0 0 0-.408 1.824c-.018.53.093 1.056.324 1.533.201.392.493.73.851.987a3.35 3.35 0 0 0 1.244.529c.493.104.995.155 1.499.153a6.555 6.555 0 0 0 2.536-.46M99.35 41.734a4.687 4.687 0 0 1-2.009-3.287h2.043c.14.966.763 1.794 1.652 2.197a7.522 7.522 0 0 0 3.288.664 5.688 5.688 0 0 0 4.173-1.345 4.997 4.997 0 0 0 1.345-3.697v-2.793h-.102a7.293 7.293 0 0 1-2.283 2.282 6.297 6.297 0 0 1-3.304.784 7.375 7.375 0 0 1-3.135-.647 6.921 6.921 0 0 1-2.385-1.806 8.095 8.095 0 0 1-1.516-2.777 11.429 11.429 0 0 1-.528-3.56 10.89 10.89 0 0 1 .613-3.799 8.483 8.483 0 0 1 1.636-2.777 6.75 6.75 0 0 1 2.402-1.703 7.45 7.45 0 0 1 2.913-.58 6.234 6.234 0 0 1 3.372.835 6.938 6.938 0 0 1 2.215 2.265h.102v-2.725h2.078v16.931a6.78 6.78 0 0 1-1.636 4.735 7.751 7.751 0 0 1-5.893 2.112 8.337 8.337 0 0 1-5.041-1.309m9.232-8.908a8.565 8.565 0 0 0 1.397-5.11 11.22 11.22 0 0 0-.34-2.862 6.239 6.239 0 0 0-1.056-2.231 4.828 4.828 0 0 0-1.789-1.448 5.772 5.772 0 0 0-2.503-.511 4.782 4.782 0 0 0-4.071 1.941 8.464 8.464 0 0 0-1.448 5.179c-.008.936.107 1.87.34 2.777a6.64 6.64 0 0 0 1.022 2.214 4.81 4.81 0 0 0 1.703 1.465 5.208 5.208 0 0 0 2.42.528 4.967 4.967 0 0 0 4.325-1.942M119.244 36.624a7.19 7.19 0 0 1-2.572-1.941 8.66 8.66 0 0 1-1.583-2.93 11.839 11.839 0 0 1-.546-3.662 11.179 11.179 0 0 1 .58-3.663 9.138 9.138 0 0 1 1.617-2.929 7.307 7.307 0 0 1 2.522-1.942 7.684 7.684 0 0 1 3.321-.698 7.275 7.275 0 0 1 3.56.8 6.678 6.678 0 0 1 2.351 2.146 8.806 8.806 0 0 1 1.278 3.083 16.87 16.87 0 0 1 .374 3.577h-13.422c.013.941.157 1.875.426 2.777a6.968 6.968 0 0 0 1.124 2.231 5.108 5.108 0 0 0 1.857 1.499 5.948 5.948 0 0 0 2.623.546 4.985 4.985 0 0 0 3.424-1.074 5.875 5.875 0 0 0 1.719-2.878h2.044a7.51 7.51 0 0 1-2.385 4.19 7.072 7.072 0 0 1-4.803 1.567 8.386 8.386 0 0 1-3.509-.699m8.312-12.264a5.986 5.986 0 0 0-.988-1.976 4.525 4.525 0 0 0-1.635-1.311 5.362 5.362 0 0 0-2.351-.478 5.623 5.623 0 0 0-2.368.478 5.064 5.064 0 0 0-1.754 1.311 6.566 6.566 0 0 0-1.141 1.96 9.615 9.615 0 0 0-.562 2.453h11.174a9.268 9.268 0 0 0-.375-2.437M134.879 19.267v2.691h.068a7.237 7.237 0 0 1 2.333-2.197 6.798 6.798 0 0 1 3.561-.868 5.834 5.834 0 0 1 4.037 1.413 5.174 5.174 0 0 1 1.584 4.071V36.88h-2.112V24.581a3.716 3.716 0 0 0-1.073-2.947 4.334 4.334 0 0 0-2.948-.937 5.896 5.896 0 0 0-2.111.375 5.558 5.558 0 0 0-1.738 1.039 4.717 4.717 0 0 0-1.601 3.593V36.88h-2.112V19.267h2.112zM151.912 36.284a2.934 2.934 0 0 1-.92-2.436V21.005h-2.657v-1.738h2.657v-5.416h2.112v5.416h3.271v1.738h-3.271v12.502a1.65 1.65 0 0 0 .426 1.312c.371.265.823.391 1.277.357.258-.001.515-.029.766-.085.215-.043.426-.106.63-.188h.103v1.806a5.907 5.907 0 0 1-1.942.306 3.819 3.819 0 0 1-2.452-.731M162.625 36.624a7.368 7.368 0 0 1-2.571-1.942 8.732 8.732 0 0 1-1.618-2.929 12.217 12.217 0 0 1 0-7.324 8.744 8.744 0 0 1 1.618-2.93 7.386 7.386 0 0 1 2.571-1.942 8.106 8.106 0 0 1 3.424-.698 7.989 7.989 0 0 1 3.406.698 7.424 7.424 0 0 1 2.556 1.942 8.504 8.504 0 0 1 1.601 2.93 12.57 12.57 0 0 1 0 7.324 8.5 8.5 0 0 1-1.601 2.929 7.415 7.415 0 0 1-2.556 1.942 7.959 7.959 0 0 1-3.406.698 8.075 8.075 0 0 1-3.424-.698m6.013-1.652a5.308 5.308 0 0 0 1.873-1.601 7.215 7.215 0 0 0 1.124-2.385 11.348 11.348 0 0 0 0-5.792 7.215 7.215 0 0 0-1.124-2.385 5.289 5.289 0 0 0-1.873-1.601 6.109 6.109 0 0 0-5.195 0 5.497 5.497 0 0 0-1.874 1.601 7.046 7.046 0 0 0-1.141 2.385 11.392 11.392 0 0 0 0 5.792c.227.86.614 1.669 1.141 2.385a5.817 5.817 0 0 0 7.069 1.601M176.856 22.191a2.128 2.128 0 0 1-2.213-2.265 2.216 2.216 0 1 1 4.431 0 2.146 2.146 0 0 1-2.218 2.265m0-4.277a1.845 1.845 0 0 0-1.892 2.012 1.9 1.9 0 1 0 3.797 0 1.854 1.854 0 0 0-1.905-2.012m.653 3.222l-.751-1.073h-.243v1.035h-.43v-2.509h.763c.526 0 .877.264.877.732a.681.681 0 0 1-.508.693l.724 1.025-.432.097zm-.661-2.157h-.333v.741h.312c.283 0 .46-.117.46-.371.001-.244-.158-.37-.439-.37"/></svg> diff --git a/lib/web/mage/adminhtml/tools.js b/lib/web/mage/adminhtml/tools.js index ed4bab7102a..27f6efcfc58 100644 --- a/lib/web/mage/adminhtml/tools.js +++ b/lib/web/mage/adminhtml/tools.js @@ -348,7 +348,7 @@ var Fieldset = { }, saveState: function (url, parameters) { new Ajax.Request(url, { - method: 'get', + method: 'post', parameters: Object.toQueryString(parameters), loaderArea: false }); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js index 48960ed3403..98ce6a005db 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js @@ -186,11 +186,19 @@ define([ }); _.each(compiled, function (node, nodeName) { - var attributes = node.attributes.join('|'), - children = node.children.join('|'); + var filteredAttributes = []; - validElements.push(nodeName + '[' + attributes + ']'); - validChildren.push(nodeName + '[' + children + ']'); + _.each(node.attributes, function (attribute) { //eslint-disable-line max-nested-callbacks + // Disallowing usage of 'on*' attributes. + if (!/^on/.test(attribute)) { + filteredAttributes.push(attribute); + } + }); + + node.attributes = filteredAttributes; + + validElements.push(nodeName + '[' + node.attributes.join('|') + ']'); + validChildren.push(nodeName + '[' + node.children.join('|') + ']'); }); return { diff --git a/lib/web/mage/menu.js b/lib/web/mage/menu.js index 86d98181724..b8000640506 100644 --- a/lib/web/mage/menu.js +++ b/lib/web/mage/menu.js @@ -85,12 +85,10 @@ define([ var controls = this.controls, toggle = this.toggle; - this._on(controls.toggleBtn, { - 'click': toggle - }); - this._on(controls.swipeArea, { - 'swipeleft': toggle - }); + controls.toggleBtn.off('click'); + controls.toggleBtn.on('click', toggle.bind(this)); + controls.swipeArea.off('swipeleft'); + controls.swipeArea.on('swipeleft', toggle.bind(this)); }, /** diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 86dd5a3b036..a5772088949 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -364,7 +364,7 @@ ], 'time12h': [ function (value, element) { - return this.optional(element) || /^((0?[1-9]|1[012])(:[0-5]\d){0,2}(\ [AP]M))$/i.test(value); + return this.optional(element) || /^((0?[1-9]|1[012])(:[0-5]\d){0,2}(\s[AP]M))$/i.test(value); }, $.mage.__('Please enter a valid time, between 00:00 am and 12:00 pm') ], diff --git a/pub/errors/default/images/logo.gif b/pub/errors/default/images/logo.gif index f1f7fcaf4f0..0cca183e08d 100644 Binary files a/pub/errors/default/images/logo.gif and b/pub/errors/default/images/logo.gif differ diff --git a/pub/static/.htaccess b/pub/static/.htaccess index f49dce25d43..a5aa6fb0d5c 100644 --- a/pub/static/.htaccess +++ b/pub/static/.htaccess @@ -22,6 +22,11 @@ Options -MultiViews RewriteCond %{REQUEST_FILENAME} !-l RewriteRule .* ../static.php?resource=$0 [L] + # Detects if moxieplayer request with uri params and redirects to uri without params + <Files moxieplayer.swf> + RewriteCond %{QUERY_STRING} !^$ + RewriteRule ^(.*)$ %{REQUEST_URI}? [R=301,L] + </Files> </IfModule> ############################################ diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 3a56237fd17..8b295c9f5fc 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -32071,7 +32071,15 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate <hashTree/> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete category" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"/> + <collectionProp name="Arguments.arguments"> + <elementProp name="form_key" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${admin_form_key}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">form_key</stringProp> + </elementProp> + </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> @@ -32080,7 +32088,7 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/delete/id/${admin_category_id}/</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.use_keepalive">true</boolProp> @@ -40755,7 +40763,7 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description\n gift_message_available\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40812,7 +40820,7 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40888,7 +40896,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40957,7 +40965,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41017,7 +41025,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41077,7 +41085,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(search: \"color\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41137,7 +41145,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41206,7 +41214,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41293,7 +41301,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41360,7 +41368,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description\n gift_message_available\n id\n image\n image_label\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description\n sku\n small_image {\n url\n }\n small_image_label\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n thumbnail_label\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> diff --git a/setup/pub/images/magento-logo.svg b/setup/pub/images/magento-logo.svg index 6dcc79d33b2..e4f627809b6 100644 --- a/setup/pub/images/magento-logo.svg +++ b/setup/pub/images/magento-logo.svg @@ -1,18 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1 Tiny//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd'> -<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="214px" xml:space="preserve" height="62px" viewBox="0 0 214 62" baseProfile="tiny" version="1.1" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink"> - <path d="m93.166 44.96l-1.809-23.096-9.17 23.221h-2.988l-9.17-23.221-1.767 23.096h-3.702l2.314-29.026h4.88l9.045 23.809 9.045-23.809h4.836l2.271 29.026h-3.785z" fill="#131108"/> - <path d="m112.94 44.96l-0.421-2.692c-1.597 1.639-3.785 3.112-7.066 3.112-3.619 0-5.889-2.188-5.889-5.596 0-5.006 4.29-6.981 12.663-7.867v-0.841c0-2.523-1.515-3.407-3.83-3.407-2.439 0-4.754 0.757-6.94 1.725l-0.505-3.238c2.398-0.969 4.67-1.682 7.783-1.682 4.88 0 7.236 1.976 7.236 6.435v14.051h-3.02zm-0.72-10.182c-7.406 0.715-8.963 2.735-8.963 4.796 0 1.642 1.095 2.693 2.989 2.693 2.187 0 4.291-1.095 5.974-2.82v-4.669z" fill="#131108"/> - <path d="m137.46 24.599l0.546 3.364-3.826 0.378c0.546 0.926 0.799 1.979 0.799 3.113 0 4.292-3.618 6.899-7.699 6.899-0.504 0-1.011-0.042-1.514-0.126-0.589 0.38-1.01 0.844-1.01 1.22 0 0.716 0.714 0.886 4.248 1.517l1.432 0.252c4.249 0.757 6.898 2.102 6.898 5.216 0 4.206-4.586 6.183-9.802 6.183s-9.381-1.64-9.381-5.173c0-2.062 1.431-3.66 4.248-5.174-0.882-0.631-1.26-1.348-1.26-2.104 0-0.969 0.756-1.936 2.103-2.734-2.229-1.095-3.744-3.238-3.744-5.974 0-4.332 3.616-6.981 7.697-6.981 2.019 0 3.786 0.587 5.175 1.682l5.08-1.558zm-15.73 22.547c0 1.599 2.06 2.775 5.972 2.775 3.913 0 6.099-1.345 6.099-3.027 0-1.222-0.924-2.061-3.784-2.566l-2.397-0.422c-1.095-0.208-1.682-0.336-2.481-0.502-2.36 1.177-3.41 2.356-3.41 3.742zm5.47-19.939c-2.522 0-4.081 1.936-4.081 4.375 0 2.313 1.6 4.12 4.081 4.12 2.566 0 4.165-1.892 4.165-4.29 0-2.397-1.68-4.205-4.16-4.205z" fill="#131108"/> - <path d="m155.3 35.325h-13.631c0.125 4.669 2.354 6.856 5.847 6.856 2.904 0 5.007-1.135 7.193-2.86l0.546 3.367c-2.144 1.682-4.709 2.691-8.031 2.691-5.219 0-9.299-3.155-9.299-10.519 0-6.435 3.787-10.388 8.835-10.388 5.846 0 8.54 4.5 8.54 10.052v0.801zm-8.58-7.908c-2.313 0-4.291 1.641-4.879 5.09h9.675c-0.47-3.239-1.9-5.09-4.8-5.09z" fill="#131108"/> - <path d="m171.07 44.96v-13.673c0-2.06-0.883-3.449-3.07-3.449-1.977 0-3.996 1.305-5.807 3.239v13.883h-3.743v-20.067h2.986l0.463 2.903c1.893-1.724 4.251-3.323 7.108-3.323 3.786 0 5.808 2.271 5.808 5.888v14.599h-3.75z" fill="#131108"/> - <path d="m185.88 45.298c-3.532 0-5.846-1.265-5.846-5.304v-11.946h-3.03v-3.156h3.03v-6.688l3.66-0.546v7.234h4.332l0.505 3.156h-4.837v11.273c0 1.643 0.675 2.651 2.776 2.651 0.673 0 1.262-0.041 1.724-0.127l0.506 3.196c-0.63 0.128-1.51 0.257-2.81 0.257z" fill="#131108"/> - <path d="m198.29 45.38c-5.342 0-9.213-3.827-9.213-10.434 0-6.605 3.871-10.473 9.213-10.473 5.383 0 9.339 3.868 9.339 10.473 0 6.607-3.96 10.434-9.34 10.434zm0-17.753c-3.617 0-5.426 3.113-5.426 7.319 0 4.125 1.892 7.321 5.426 7.321 3.702 0 5.553-3.114 5.553-7.321 0-4.122-1.93-7.319-5.55-7.319z" fill="#131108"/> - <path d="m210.28 27.897c-1.505 0-2.551-1.045-2.551-2.606 0-1.55 1.067-2.618 2.551-2.618 1.505 0 2.55 1.056 2.55 2.618 0 1.55-1.07 2.606-2.55 2.606zm0-4.92c-1.214 0-2.18 0.831-2.18 2.314 0 1.472 0.966 2.303 2.18 2.303 1.225 0 2.191-0.832 2.191-2.303 0-1.483-0.98-2.314-2.19-2.314zm0.75 3.708l-0.863-1.237h-0.281v1.191h-0.495v-2.888h0.878c0.606 0 1.01 0.303 1.01 0.843 0 0.416-0.225 0.686-0.585 0.798l0.833 1.18-0.5 0.113zm-0.76-2.484h-0.383v0.854h0.359c0.325 0 0.53-0.135 0.53-0.427 0-0.281-0.18-0.427-0.51-0.427z" fill="#131108"/> - <g fill="#E85D22"> - <path d="m26.845 8.857"/> - <polygon points="53.692 15.5 53.692 46.5 46.021 50.929 46.021 19.929 26.845 8.857 7.67 19.928 7.67 50.929 0 46.5 0 15.5 26.845 0"/> - <polygon points="26.847 62 15.341 55.356 15.341 24.357 23.011 19.928 23.011 50.929 26.845 53.257 30.682 50.929 30.682 19.929 38.353 24.357 38.353 55.356"/> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.07329 60.13148"><defs><style>.a{fill:#f26322;}.b{fill:#4d4d4d;}</style></defs><title>Magento-an-Adobe-Company-logo-horizontal diff --git a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php index 00fa272e749..173ea9e49a8 100644 --- a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php +++ b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php @@ -15,6 +15,9 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; +/** + * Command to create an admin user. + */ class AdminUserCreateCommand extends AbstractSetupCommand { /** @@ -52,6 +55,8 @@ protected function configure() } /** + * Creation admin user in interaction mode. + * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output * @@ -129,6 +134,8 @@ protected function interact(InputInterface $input, OutputInterface $output) } /** + * Add not empty validator. + * * @param \Symfony\Component\Console\Question\Question $question * @return void */ @@ -144,7 +151,7 @@ private function addNotEmptyValidator(Question $question) } /** - * {@inheritdoc} + * @inheritdoc */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -165,25 +172,43 @@ protected function execute(InputInterface $input, OutputInterface $output) /** * Get list of arguments for the command * + * @param int $mode The mode of options. * @return InputOption[] */ - public function getOptionsList() + public function getOptionsList($mode = InputOption::VALUE_REQUIRED) { + $requiredStr = ($mode === InputOption::VALUE_REQUIRED ? '(Required) ' : ''); + return [ - new InputOption(AdminAccount::KEY_USER, null, InputOption::VALUE_REQUIRED, '(Required) Admin user'), - new InputOption(AdminAccount::KEY_PASSWORD, null, InputOption::VALUE_REQUIRED, '(Required) Admin password'), - new InputOption(AdminAccount::KEY_EMAIL, null, InputOption::VALUE_REQUIRED, '(Required) Admin email'), + new InputOption( + AdminAccount::KEY_USER, + null, + $mode, + $requiredStr . 'Admin user' + ), + new InputOption( + AdminAccount::KEY_PASSWORD, + null, + $mode, + $requiredStr . 'Admin password' + ), + new InputOption( + AdminAccount::KEY_EMAIL, + null, + $mode, + $requiredStr . 'Admin email' + ), new InputOption( AdminAccount::KEY_FIRST_NAME, null, - InputOption::VALUE_REQUIRED, - '(Required) Admin first name' + $mode, + $requiredStr . 'Admin first name' ), new InputOption( AdminAccount::KEY_LAST_NAME, null, - InputOption::VALUE_REQUIRED, - '(Required) Admin last name' + $mode, + $requiredStr . 'Admin last name' ), ]; } diff --git a/setup/src/Magento/Setup/Console/Command/InstallCommand.php b/setup/src/Magento/Setup/Console/Command/InstallCommand.php index d94beff15b3..74c2e3b2423 100644 --- a/setup/src/Magento/Setup/Console/Command/InstallCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InstallCommand.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Setup\Console\Command; use Magento\Deploy\Console\Command\App\ConfigImportCommand; use Magento\Framework\Setup\Declaration\Schema\DryRunLogger; use Magento\Framework\Setup\Declaration\Schema\OperationsExecutor; use Magento\Framework\Setup\Declaration\Schema\Request; +use Magento\Setup\Model\AdminAccount; use Magento\Setup\Model\ConfigModel; use Magento\Setup\Model\InstallerFactory; use Magento\Framework\Setup\ConsoleLogger; @@ -45,20 +48,20 @@ class InstallCommand extends AbstractSetupCommand * List of comma-separated module names. That must be enabled during installation. * Available magic param all. */ - const INPUT_KEY_ENABLE_MODULES = 'enable_modules'; + const INPUT_KEY_ENABLE_MODULES = 'enable-modules'; /** * List of comma-separated module names. That must be avoided during installation. * List of comma-separated module names. That must be avoided during installation. * Available magic param all. */ - const INPUT_KEY_DISABLE_MODULES = 'disable_modules'; + const INPUT_KEY_DISABLE_MODULES = 'disable-modules'; /** * If this flag is enabled, than all your old scripts with format: * InstallSchema, UpgradeSchema will be converted to new db_schema.xml format. */ - const CONVERT_OLD_SCRIPTS_KEY = 'convert_old_scripts'; + const CONVERT_OLD_SCRIPTS_KEY = 'convert-old-scripts'; /** * Parameter indicating command for interactive setup @@ -129,13 +132,13 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function configure() { $inputOptions = $this->configModel->getAvailableOptions(); $inputOptions = array_merge($inputOptions, $this->userConfig->getOptionsList()); - $inputOptions = array_merge($inputOptions, $this->adminUser->getOptionsList()); + $inputOptions = array_merge($inputOptions, $this->adminUser->getOptionsList(InputOption::VALUE_OPTIONAL)); $inputOptions = array_merge($inputOptions, [ new InputOption( self::INPUT_KEY_CLEANUP_DB, @@ -155,12 +158,6 @@ protected function configure() InputOption::VALUE_NONE, 'Use sample data' ), - new InputOption( - Request::DUMP_ENABLE_OPTIONS, - null, - InputOption::VALUE_REQUIRED, - 'Should removed columns be dumped or recovered columns data reverted.' - ), new InputOption( self::INPUT_KEY_ENABLE_MODULES, null, @@ -215,7 +212,7 @@ protected function configure() } /** - * {@inheritdoc} + * @inheritdoc */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -230,7 +227,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } /** - * {@inheritdoc} + * @inheritdoc */ protected function initialize(InputInterface $input, OutputInterface $output) { @@ -256,7 +253,7 @@ protected function initialize(InputInterface $input, OutputInterface $output) } $errors = $this->configModel->validate($configOptionsToValidate); - $errors = array_merge($errors, $this->adminUser->validate($input)); + $errors = array_merge($errors, $this->validateAdmin($input)); $errors = array_merge($errors, $this->validate($input)); $errors = array_merge($errors, $this->userConfig->validate($input)); @@ -277,11 +274,11 @@ protected function initialize(InputInterface $input, OutputInterface $output) * @param InputInterface $input * @return string[] Array of error messages */ - public function validate(InputInterface $input) + public function validate(InputInterface $input) : array { $errors = []; $value = $input->getOption(self::INPUT_KEY_SALES_ORDER_INCREMENT_PREFIX); - if (preg_match(self::SALES_ORDER_INCREMENT_PREFIX_RULE, $value) != 1) { + if (preg_match(self::SALES_ORDER_INCREMENT_PREFIX_RULE, (string) $value) != 1) { $errors[] = 'Validation failed, ' . self::INPUT_KEY_SALES_ORDER_INCREMENT_PREFIX . ' must be 20 characters or less'; } @@ -297,7 +294,7 @@ public function validate(InputInterface $input) * @param OutputInterface $output * @return string[] Array of inputs */ - private function interactiveQuestions(InputInterface $input, OutputInterface $output) + private function interactiveQuestions(InputInterface $input, OutputInterface $output) : array { $helper = $this->getHelper('question'); $configOptionsToValidate = []; @@ -325,7 +322,7 @@ private function interactiveQuestions(InputInterface $input, OutputInterface $ou $output->writeln(""); - foreach ($this->adminUser->getOptionsList() as $option) { + foreach ($this->adminUser->getOptionsList(InputOption::VALUE_OPTIONAL) as $option) { $configOptionsToValidate[$option->getName()] = $this->askQuestion( $input, $output, @@ -416,4 +413,24 @@ private function askQuestion( return $value; } + + /** + * Performs validation of admin options if at least one of them was set. + * + * @param InputInterface $input + * @return array + */ + private function validateAdmin(InputInterface $input): array + { + if ($input->getOption(AdminAccount::KEY_FIRST_NAME) + || $input->getOption(AdminAccount::KEY_LAST_NAME) + || $input->getOption(AdminAccount::KEY_EMAIL) + || $input->getOption(AdminAccount::KEY_USER) + || $input->getOption(AdminAccount::KEY_PASSWORD) + ) { + return $this->adminUser->validate($input); + } + + return []; + } } diff --git a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php index bd7be01a0b2..2df8cde0865 100644 --- a/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php +++ b/setup/src/Magento/Setup/Console/Command/UpgradeCommand.php @@ -47,10 +47,9 @@ class UpgradeCommand extends AbstractSetupCommand private $appState; /** - * Constructor. - * * @param InstallerFactory $installerFactory * @param DeploymentConfig $deploymentConfig + * @param AppState|null $appState */ public function __construct( InstallerFactory $installerFactory, @@ -102,7 +101,7 @@ protected function configure() InputOption::VALUE_OPTIONAL, 'Magento Installation will be run in dry-run mode', false - ), + ) ]; $this->setName('setup:upgrade') ->setDescription('Upgrades the Magento application, DB data, and schema') diff --git a/setup/src/Magento/Setup/Model/DeclarationInstaller.php b/setup/src/Magento/Setup/Model/DeclarationInstaller.php index 52c360251c2..4313b031a16 100644 --- a/setup/src/Magento/Setup/Model/DeclarationInstaller.php +++ b/setup/src/Magento/Setup/Model/DeclarationInstaller.php @@ -7,7 +7,6 @@ use Magento\Framework\Setup\Declaration\Schema\Diff\SchemaDiff; use Magento\Framework\Setup\Declaration\Schema\OperationsExecutor; -use Magento\Framework\Setup\Declaration\Schema\RequestFactory; use Magento\Framework\Setup\Declaration\Schema\SchemaConfigInterface; /** @@ -25,11 +24,6 @@ class DeclarationInstaller */ private $schemaDiff; - /** - * @var RequestFactory - */ - private $requestFactory; - /** * @var SchemaConfigInterface */ @@ -41,16 +35,13 @@ class DeclarationInstaller * @param SchemaConfigInterface $schemaConfig * @param SchemaDiff $schemaDiff * @param OperationsExecutor $operationsExecutor - * @param RequestFactory $requestFactory */ public function __construct( SchemaConfigInterface $schemaConfig, SchemaDiff $schemaDiff, - OperationsExecutor $operationsExecutor, - RequestFactory $requestFactory + OperationsExecutor $operationsExecutor ) { $this->operationsExecutor = $operationsExecutor; - $this->requestFactory = $requestFactory; $this->schemaConfig = $schemaConfig; $this->schemaDiff = $schemaDiff; } diff --git a/setup/src/Magento/Setup/Model/Installer.php b/setup/src/Magento/Setup/Model/Installer.php index a1a62dc5946..ad24307ac18 100644 --- a/setup/src/Magento/Setup/Model/Installer.php +++ b/setup/src/Magento/Setup/Model/Installer.php @@ -56,8 +56,8 @@ class Installer /**#@+ * Parameters for enabling/disabling modules */ - const ENABLE_MODULES = 'enable_modules'; - const DISABLE_MODULES = 'disable_modules'; + const ENABLE_MODULES = 'enable-modules'; + const DISABLE_MODULES = 'disable-modules'; /**#@- */ /**#@+ @@ -267,8 +267,7 @@ class Installer * @param \Magento\Framework\Setup\SampleData\State $sampleDataState * @param ComponentRegistrar $componentRegistrar * @param PhpReadinessCheck $phpReadinessCheck - * - * @param DeclarationInstaller|null $declarationInstaller + * @throws \Magento\Setup\Exception * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -346,7 +345,9 @@ public function install($request) [$request[InstallCommand::INPUT_KEY_SALES_ORDER_INCREMENT_PREFIX]], ]; } - $script[] = ['Installing admin user...', 'installAdminUser', [$request]]; + if ($this->isAdminDataSet($request)) { + $script[] = ['Installing admin user...', 'installAdminUser', [$request]]; + } if (!$this->isDryRun($request)) { $script[] = ['Caches clearing:', 'cleanCaches', [$request]]; @@ -410,7 +411,7 @@ private function writeInstallationDate() } /** - * Creates modules deployment configuration segment + * Create modules deployment configuration segment * * @param \ArrayObject|array $request * @param bool $dryRun @@ -423,8 +424,8 @@ private function createModulesConfig($request, $dryRun = false) $deploymentConfig = $this->deploymentConfigReader->load(); $currentModules = isset($deploymentConfig[ConfigOptionsListConstants::KEY_MODULES]) ? $deploymentConfig[ConfigOptionsListConstants::KEY_MODULES] : []; - $enable = $this->readListOfModules($all, $request, self::ENABLE_MODULES); - $disable = $this->readListOfModules($all, $request, self::DISABLE_MODULES); + $enable = $this->readListOfModules($all, $request, InstallCommand::INPUT_KEY_ENABLE_MODULES); + $disable = $this->readListOfModules($all, $request, InstallCommand::INPUT_KEY_DISABLE_MODULES); $result = []; foreach ($all as $module) { if ((isset($currentModules[$module]) && !$currentModules[$module])) { @@ -904,12 +905,13 @@ private function throwExceptionForNotWritablePaths(array $paths) } /** - * Handles database schema and data (install/upgrade/backup/uninstall etc) + * Handle database schema and data (install/upgrade/backup/uninstall etc) * - * @param SchemaSetupInterface | ModuleDataSetupInterface $setup + * @param SchemaSetupInterface|ModuleDataSetupInterface $setup * @param string $type * @param array $request * @return void + * @throws \Magento\Framework\Setup\Exception * @throws \Magento\Setup\Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -1027,6 +1029,8 @@ private function handleDBSchemaData($setup, $type, array $request) } /** + * Assert DbConfigExists + * * @return void * @throws \Magento\Setup\Exception */ @@ -1082,7 +1086,8 @@ public function installUserConfig($data) } /** - * Creates data handler + * Create data handler + * * @param string $className * @param string $interfaceName * @return mixed|null @@ -1101,7 +1106,7 @@ protected function createSchemaDataHandler($className, $interfaceName) } /** - * Creates store order increment prefix configuration + * Create store order increment prefix configuration * * @param string $orderIncrementPrefix Value to use for order increment prefix * @return void @@ -1145,7 +1150,7 @@ private function installOrderIncrementPrefix($orderIncrementPrefix) } /** - * Creates admin account + * Create admin account * * @param \ArrayObject|array $data * @return void @@ -1471,4 +1476,28 @@ private function cleanupGeneratedFiles() $this->log->log($message); } } + + /** + * Checks that admin data is not empty in request array + * + * @param \ArrayObject|array $request + * @return bool + */ + private function isAdminDataSet($request) + { + $adminData = array_filter($request, function ($value, $key) { + return in_array( + $key, + [ + AdminAccount::KEY_EMAIL, + AdminAccount::KEY_FIRST_NAME, + AdminAccount::KEY_LAST_NAME, + AdminAccount::KEY_USER, + AdminAccount::KEY_PASSWORD, + ] + ) && $value !== null; + }, ARRAY_FILTER_USE_BOTH); + + return !empty($adminData); + } } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php index a80302b40a6..b90fa66b3d4 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php @@ -11,8 +11,12 @@ use Magento\User\Model\UserValidationRules; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Tester\CommandTester; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AdminUserCreateCommandTest extends \PHPUnit\Framework\TestCase { /** @@ -125,11 +129,34 @@ public function testInteraction() ); } - public function testGetOptionsList() + /** + * @param int $mode + * @param string $description + * @dataProvider getOptionListDataProvider + */ + public function testGetOptionsList($mode, $description) { /* @var $argsList \Symfony\Component\Console\Input\InputArgument[] */ - $argsList = $this->command->getOptionsList(); + $argsList = $this->command->getOptionsList($mode); $this->assertEquals(AdminAccount::KEY_EMAIL, $argsList[2]->getName()); + $this->assertEquals($description, $argsList[2]->getDescription()); + } + + /** + * @return array + */ + public function getOptionListDataProvider() + { + return [ + [ + 'mode' => InputOption::VALUE_REQUIRED, + 'description' => '(Required) Admin email', + ], + [ + 'mode' => InputOption::VALUE_OPTIONAL, + 'description' => 'Admin email', + ], + ]; } /** diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/DbSchemaUpgradeCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/DbSchemaUpgradeCommandTest.php index f36675e26fa..61c004c2e0b 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/DbSchemaUpgradeCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/DbSchemaUpgradeCommandTest.php @@ -3,10 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Setup\Test\Unit\Console\Command; -use Magento\Framework\Module\ModuleList; use Magento\Setup\Console\Command\DbSchemaUpgradeCommand; use Symfony\Component\Console\Tester\CommandTester; @@ -58,10 +58,10 @@ public function executeDataProvider() [ 'options' => [ '--magento-init-params' => '', - '--convert_old_scripts' => false + '--convert-old-scripts' => false ], 'expectedOptions' => [ - 'convert_old_scripts' => false, + 'convert-old-scripts' => false, 'magento-init-params' => '', ] ], diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php index 5b7b6c16269..3c3a875a278 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php @@ -16,6 +16,7 @@ use Magento\Backend\Setup\ConfigOptionsList as BackendConfigOptionsList; use Magento\Framework\Config\ConfigOptionsListConstants as SetupConfigOptionsList; use Magento\Setup\Model\StoreConfigurationDataMapper; +use Magento\Setup\Console\Command\AdminUserCreateCommand; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -62,6 +63,11 @@ class InstallCommandTest extends \PHPUnit\Framework\TestCase */ private $configImportMock; + /** + * @var AdminUserCreateCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $adminUserMock; + public function setUp() { $this->input = [ @@ -73,11 +79,6 @@ public function setUp() '--' . StoreConfigurationDataMapper::KEY_LANGUAGE => 'en_US', '--' . StoreConfigurationDataMapper::KEY_TIMEZONE => 'America/Chicago', '--' . StoreConfigurationDataMapper::KEY_CURRENCY => 'USD', - '--' . AdminAccount::KEY_USER => 'user', - '--' . AdminAccount::KEY_PASSWORD => '123123q', - '--' . AdminAccount::KEY_EMAIL => 'test@test.com', - '--' . AdminAccount::KEY_FIRST_NAME => 'John', - '--' . AdminAccount::KEY_LAST_NAME => 'Doe', ]; $configModel = $this->createMock(\Magento\Setup\Model\ConfigModel::class); @@ -100,15 +101,11 @@ public function setUp() ->method('validate') ->will($this->returnValue([])); - $adminUser = $this->createMock(\Magento\Setup\Console\Command\AdminUserCreateCommand::class); - $adminUser + $this->adminUserMock = $this->createMock(AdminUserCreateCommand::class); + $this->adminUserMock ->expects($this->once()) ->method('getOptionsList') ->will($this->returnValue($this->getOptionsListAdminUser())); - $adminUser - ->expects($this->once()) - ->method('validate') - ->will($this->returnValue([])); $this->installerFactory = $this->createMock(\Magento\Setup\Model\InstallerFactory::class); $this->installer = $this->createMock(\Magento\Setup\Model\Installer::class); @@ -143,7 +140,7 @@ public function setUp() $this->installerFactory, $configModel, $userConfig, - $adminUser + $this->adminUserMock ); $this->command->setApplication( $this->applicationMock @@ -152,6 +149,16 @@ public function setUp() public function testExecute() { + $this->input['--' . AdminAccount::KEY_USER] = 'user'; + $this->input['--' . AdminAccount::KEY_PASSWORD] = '123123q'; + $this->input['--' . AdminAccount::KEY_EMAIL] = 'test@test.com'; + $this->input['--' . AdminAccount::KEY_FIRST_NAME] = 'John'; + $this->input['--' . AdminAccount::KEY_LAST_NAME] = 'Doe'; + + $this->adminUserMock + ->expects($this->once()) + ->method('validate') + ->willReturn([]); $this->installerFactory->expects($this->once()) ->method('create') ->will($this->returnValue($this->installer)); @@ -269,6 +276,9 @@ private function getOptionsListAdminUser() */ public function testValidate($prefixValue) { + $this->adminUserMock + ->expects($this->never()) + ->method('validate'); $this->installerFactory->expects($this->once()) ->method('create') ->will($this->returnValue($this->installer)); @@ -288,6 +298,9 @@ public function testValidate($prefixValue) */ public function testValidateWithException($prefixValue) { + $this->adminUserMock + ->expects($this->never()) + ->method('validate'); $this->installerFactory->expects($this->never()) ->method('create') ->will($this->returnValue($this->installer)); diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php index 6f32a686826..3d46736e449 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/UpgradeCommandTest.php @@ -104,61 +104,61 @@ public function executeDataProvider() [ 'options' => [ '--magento-init-params' => '', - '--convert_old_scripts' => false, + '--convert-old-scripts' => false, ], 'deployMode' => \Magento\Framework\App\State::MODE_PRODUCTION, 'expectedString' => 'Please re-run Magento compile command. Use the command "setup:di:compile"' . PHP_EOL, 'expectedOptions' => [ 'keep-generated' => false, - 'convert_old_scripts' => false, - 'magento-init-params' => '', + 'convert-old-scripts' => false, 'safe-mode' => false, 'data-restore' => false, 'dry-run' => false, + 'magento-init-params' => '', ] ], [ 'options' => [ '--magento-init-params' => '', - '--convert_old_scripts' => false, + '--convert-old-scripts' => false, '--keep-generated' => true, ], 'deployMode' => \Magento\Framework\App\State::MODE_PRODUCTION, 'expectedString' => '', 'expectedOptions' => [ 'keep-generated' => true, - 'convert_old_scripts' => false, - 'magento-init-params' => '', + 'convert-old-scripts' => false, 'safe-mode' => false, 'data-restore' => false, 'dry-run' => false, + 'magento-init-params' => '', ] ], [ - 'options' => ['--magento-init-params' => '', '--convert_old_scripts' => false], + 'options' => ['--magento-init-params' => '', '--convert-old-scripts' => false], 'deployMode' => \Magento\Framework\App\State::MODE_DEVELOPER, 'expectedString' => '', 'expectedOptions' => [ 'keep-generated' => false, - 'convert_old_scripts' => false, - 'magento-init-params' => '', + 'convert-old-scripts' => false, 'safe-mode' => false, 'data-restore' => false, 'dry-run' => false, + 'magento-init-params' => '', ] ], [ - 'options' => ['--magento-init-params' => '', '--convert_old_scripts' => false], + 'options' => ['--magento-init-params' => '', '--convert-old-scripts' => false], 'deployMode' => \Magento\Framework\App\State::MODE_DEFAULT, 'expectedString' => '', 'expectedOptions' => [ 'keep-generated' => false, - 'convert_old_scripts' => false, - 'magento-init-params' => '', + 'convert-old-scripts' => false, 'safe-mode' => false, 'data-restore' => false, 'dry-run' => false, + 'magento-init-params' => '', ] ], ]; diff --git a/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php b/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php index 8a4263be18e..e600002d535 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php @@ -9,6 +9,7 @@ use Magento\Backend\Setup\ConfigOptionsList; use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\Setup\SchemaListener; + use Magento\Setup\Model\AdminAccount; use Magento\Setup\Model\DeclarationInstaller; use Magento\Setup\Model\Installer; use Magento\Framework\App\Filesystem\DirectoryList; @@ -268,17 +269,13 @@ private function createObject($connectionFactory = false, $objectManagerProvider } /** + * @param array $request + * @param array $logMessages + * @dataProvider installDataProvider * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testInstall() + public function testInstall(array $request, array $logMessages) { - $request = [ - ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', - ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', - ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', - ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', - ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', - ]; $this->config->expects($this->atLeastOnce()) ->method('get') ->willReturnMap( @@ -353,7 +350,7 @@ public function testInstall() [\Magento\Setup\Model\DeclarationInstaller::class, $this->declarationInstallerMock], [\Magento\Framework\Registry::class, $registry] ])); - $this->adminFactory->expects($this->once())->method('create')->willReturn( + $this->adminFactory->expects($this->any())->method('create')->willReturn( $this->createMock(\Magento\Setup\Model\AdminAccount::class) ); $this->sampleDataState->expects($this->once())->method('hasError')->willReturn(true); @@ -366,11 +363,119 @@ public function testInstall() $this->filePermissions->expects($this->once()) ->method('getMissingWritableDirectoriesForDbUpgrade') ->willReturn([]); - $this->setupLoggerExpectsForInstall(); + call_user_func_array( + [ + $this->logger->expects($this->exactly(count($logMessages)))->method('log'), + 'withConsecutive' + ], + $logMessages + ); + $this->logger->expects($this->exactly(2)) + ->method('logSuccess') + ->withConsecutive( + ['Magento installation complete.'], + ['Magento Admin URI: /'] + ); $this->object->install($request); } + /** + * @return array + */ + public function installDataProvider() + { + return [ + [ + 'request' => [ + ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', + ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', + ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', + ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', + ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', + ], + 'logMessages' => [ + ['Starting Magento installation:'], + ['File permissions check...'], + ['Required extensions check...'], + ['Enabling Maintenance Mode...'], + ['Installing deployment configuration...'], + ['Installing database schema:'], + ['Schema creation/updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Schema post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Installing user configuration...'], + ['Enabling caches:'], + ['Current status:'], + [''], + ['Installing data...'], + ['Data install/update:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Data post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + //['Installing admin user...'], + ['Caches clearing:'], + ['Cache cleared successfully'], + ['Disabling Maintenance Mode:'], + ['Post installation file permissions check...'], + ['Write installation date...'], + ['Sample Data is installed with errors. See log file for details'] + ], + ], + [ + 'request' => [ + ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', + ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', + ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', + ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', + ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', + AdminAccount::KEY_USER => 'admin', + AdminAccount::KEY_PASSWORD => '123', + AdminAccount::KEY_EMAIL => 'admin@example.com', + AdminAccount::KEY_FIRST_NAME => 'John', + AdminAccount::KEY_LAST_NAME => 'Doe', + ], + 'logMessages' => [ + ['Starting Magento installation:'], + ['File permissions check...'], + ['Required extensions check...'], + ['Enabling Maintenance Mode...'], + ['Installing deployment configuration...'], + ['Installing database schema:'], + ['Schema creation/updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Schema post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Installing user configuration...'], + ['Enabling caches:'], + ['Current status:'], + [''], + ['Installing data...'], + ['Data install/update:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Data post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Installing admin user...'], + ['Caches clearing:'], + ['Cache cleared successfully'], + ['Disabling Maintenance Mode:'], + ['Post installation file permissions check...'], + ['Write installation date...'], + ['Sample Data is installed with errors. See log file for details'] + ], + ], + ]; + } + public function testCheckInstallationFilePermissions() { $this->filePermissions @@ -590,44 +695,6 @@ private function prepareForUpdateModulesTests() return $newObject; } - - /** - * Set up logger expectations for install method - * @return void - */ - private function setupLoggerExpectsForInstall() - { - $this->logger->expects($this->at(0))->method('log')->with('Starting Magento installation:'); - $this->logger->expects($this->at(1))->method('log')->with('File permissions check...'); - $this->logger->expects($this->at(3))->method('log')->with('Required extensions check...'); - // at(2) invokes logMeta() - $this->logger->expects($this->at(5))->method('log')->with('Enabling Maintenance Mode...'); - // at(4) - logMeta and so on... - $this->logger->expects($this->at(7))->method('log')->with('Installing deployment configuration...'); - $this->logger->expects($this->at(9))->method('log')->with('Installing database schema:'); - $this->logger->expects($this->at(11))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(13))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(15))->method('log')->with('Schema post-updates:'); - $this->logger->expects($this->at(16))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(18))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(21))->method('log')->with('Installing user configuration...'); - $this->logger->expects($this->at(23))->method('log')->with('Enabling caches:'); - $this->logger->expects($this->at(27))->method('log')->with('Installing data...'); - $this->logger->expects($this->at(28))->method('log')->with('Data install/update:'); - $this->logger->expects($this->at(29))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(31))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(33))->method('log')->with('Data post-updates:'); - $this->logger->expects($this->at(34))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(36))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(39))->method('log')->with('Installing admin user...'); - $this->logger->expects($this->at(41))->method('log')->with('Caches clearing:'); - $this->logger->expects($this->at(44))->method('log')->with('Disabling Maintenance Mode:'); - $this->logger->expects($this->at(46))->method('log')->with('Post installation file permissions check...'); - $this->logger->expects($this->at(48))->method('log')->with('Write installation date...'); - $this->logger->expects($this->at(50))->method('logSuccess')->with('Magento installation complete.'); - $this->logger->expects($this->at(52))->method('log') - ->with('Sample Data is installed with errors. See log file for details'); - } } } diff --git a/setup/view/magento/setup/marketplace-credentials.phtml b/setup/view/magento/setup/marketplace-credentials.phtml index 32ae5123136..8bd0bb0438d 100644 --- a/setup/view/magento/setup/marketplace-credentials.phtml +++ b/setup/view/magento/setup/marketplace-credentials.phtml @@ -7,7 +7,7 @@