diff --git a/app/code/Magento/Config/Model/Config/Backend/File/Pdf.php b/app/code/Magento/Config/Model/Config/Backend/File/Pdf.php new file mode 100644 index 0000000000000..8716fe5a23ad3 --- /dev/null +++ b/app/code/Magento/Config/Model/Config/Backend/File/Pdf.php @@ -0,0 +1,21 @@ + - */ namespace Magento\Config\Model\Config\Backend\Image; /** + * System config PDF field backend model. + * * @api * @since 100.0.2 + * @see \Magento\Config\Model\Config\Backend\File\Pdf */ class Pdf extends \Magento\Config\Model\Config\Backend\Image { /** + * Returns the list of allowed file extensions. + * * @return string[] */ protected function _getAllowedExtensions() { - return ['tif', 'tiff', 'png', 'jpg', 'jpe', 'jpeg']; + return ['tif', 'tiff', 'png', 'jpg', 'jpe', 'jpeg', 'pdf']; } } diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml index 543aea7d8297f..20b62ef060309 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml @@ -14,5 +14,6 @@ + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/SelectAllDownloadableLinksDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/SelectAllDownloadableLinksDownloadableProductTest.xml new file mode 100644 index 0000000000000..94940f0e08195 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/SelectAllDownloadableLinksDownloadableProductTest.xml @@ -0,0 +1,101 @@ + + + + + + + + + + <description value="All the downloadable links must be selected or unselected when anyone click on select all or unselect all checkbox respectively."/> + <severity value="MAJOR"/> + <group value="Downloadable"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" + parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" + stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Step 1: Navigate to store front Product page as guest --> + <amOnPage url="/{{DownloadableProduct.sku}}.html" + stepKey="amOnStorefrontProductPage"/> + + <!-- Step 2: click on select all checkbox --> + <click + selector="{{StorefrontDownloadableProductSection.downloadableLinkSelectAllCheckbox}}" + stepKey="selectAllProductLink"/> + + <!-- Step 3: Make sure that all product links are checked --> + <seeCheckboxIsChecked selector="{{StorefrontDownloadableProductSection.downloadableLinkByTitle(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeFirstCheckboxChecked"/> + + <seeCheckboxIsChecked selector="{{StorefrontDownloadableProductSection.downloadableLinkByTitle(downloadableLink.title)}}" stepKey="seeSecondCheckboxChecked"/> + + <!-- Step 4: click again on select all checkbox --> + <click + selector="{{StorefrontDownloadableProductSection.downloadableLinkSelectAllCheckbox}}" + stepKey="unselectAllProductLink"/> + + <!-- Step 5: Make sure that all product links are unchecked --> + <dontSeeCheckboxIsChecked selector="{{StorefrontDownloadableProductSection.downloadableLinkByTitle(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeFirstCheckboxUnChecked"/> + + <dontSeeCheckboxIsChecked selector="{{StorefrontDownloadableProductSection.downloadableLinkByTitle(downloadableLink.title)}}" stepKey="seeSecondCheckboxUnChecked"/> + + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Model/Rule/Action/SimpleActionOptionsProvider.php b/app/code/Magento/SalesRule/Model/Rule/Action/SimpleActionOptionsProvider.php new file mode 100644 index 0000000000000..a0fd4bf576f61 --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Rule/Action/SimpleActionOptionsProvider.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Rule\Action; + +use Magento\Framework\Data\OptionSourceInterface; +use Magento\SalesRule\Model\Rule; + +/** + * Class SimpleActionOptionsProvider + */ +class SimpleActionOptionsProvider implements OptionSourceInterface +{ + /** + * @inheritdoc + */ + public function toOptionArray() + { + return [ + ['label' => __('Percent of product price discount'), 'value' => Rule::BY_PERCENT_ACTION], + ['label' => __('Fixed amount discount'), 'value' => Rule::BY_FIXED_ACTION], + ['label' => __('Fixed amount discount for whole cart'), 'value' => Rule::CART_FIXED_ACTION], + ['label' => __('Buy X get Y free (discount amount is Y)'), 'value' => Rule::BUY_X_GET_Y_ACTION] + ]; + } +} diff --git a/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php b/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php index fdd6c2b169a7d..e4aaaec98dc79 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php +++ b/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php @@ -5,11 +5,14 @@ */ namespace Magento\SalesRule\Model\Rule\Metadata; -use Magento\SalesRule\Model\Rule; -use Magento\Store\Model\System\Store; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Convert\DataObject; +use Magento\SalesRule\Model\Rule; +use Magento\SalesRule\Model\Rule\Action\SimpleActionOptionsProvider; +use Magento\SalesRule\Model\RuleFactory; +use Magento\Store\Model\System\Store; /** * Metadata provider for sales rule edit form. @@ -37,10 +40,15 @@ class ValueProvider protected $objectConverter; /** - * @var \Magento\SalesRule\Model\RuleFactory + * @var RuleFactory */ protected $salesRuleFactory; + /** + * @var SimpleActionOptionsProvider + */ + private $simpleActionOptionsProvider; + /** * Initialize dependencies. * @@ -48,20 +56,24 @@ class ValueProvider * @param GroupRepositoryInterface $groupRepository * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param DataObject $objectConverter - * @param \Magento\SalesRule\Model\RuleFactory $salesRuleFactory + * @param RuleFactory $salesRuleFactory + * @param SimpleActionOptionsProvider|null $simpleActionOptionsProvider */ public function __construct( Store $store, GroupRepositoryInterface $groupRepository, SearchCriteriaBuilder $searchCriteriaBuilder, DataObject $objectConverter, - \Magento\SalesRule\Model\RuleFactory $salesRuleFactory + RuleFactory $salesRuleFactory, + SimpleActionOptionsProvider $simpleActionOptionsProvider = null ) { $this->store = $store; $this->groupRepository = $groupRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->objectConverter = $objectConverter; $this->salesRuleFactory = $salesRuleFactory; + $this->simpleActionOptionsProvider = $simpleActionOptionsProvider ?: + ObjectManager::getInstance()->get(SimpleActionOptionsProvider::class); } /** @@ -71,15 +83,10 @@ public function __construct( * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function getMetadataValues(\Magento\SalesRule\Model\Rule $rule) + public function getMetadataValues(Rule $rule) { $customerGroups = $this->groupRepository->getList($this->searchCriteriaBuilder->create())->getItems(); - $applyOptions = [ - ['label' => __('Percent of product price discount'), 'value' => Rule::BY_PERCENT_ACTION], - ['label' => __('Fixed amount discount'), 'value' => Rule::BY_FIXED_ACTION], - ['label' => __('Fixed amount discount for whole cart'), 'value' => Rule::CART_FIXED_ACTION], - ['label' => __('Buy X get Y free (discount amount is Y)'), 'value' => Rule::BUY_X_GET_Y_ACTION] - ]; + $applyOptions = $this->simpleActionOptionsProvider->toOptionArray(); $couponTypesOptions = []; $couponTypes = $this->salesRuleFactory->create()->getCouponTypes(); diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/SimpleActionOptionsProviderTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/SimpleActionOptionsProviderTest.php new file mode 100644 index 0000000000000..f1653dd043b50 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Action/SimpleActionOptionsProviderTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\SalesRule\Test\Unit\Model\Rule\Action; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\SalesRule\Model\Rule; +use Magento\SalesRule\Model\Rule\Action\SimpleActionOptionsProvider; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @covers Magento\SalesRule\Model\Rule\Action\SimpleActionOptionsProvider + */ +class SimpleActionOptionsProviderTest extends TestCase +{ + /** + * @var SimpleActionOptionsProvider|MockObject + */ + protected $model; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->model = $objectManager->getObject(SimpleActionOptionsProvider::class); + } + + public function testToOptionArray() + { + $expected = [ + ['label' => __('Percent of product price discount'), 'value' => Rule::BY_PERCENT_ACTION], + ['label' => __('Fixed amount discount'), 'value' => Rule::BY_FIXED_ACTION], + ['label' => __('Fixed amount discount for whole cart'), 'value' => Rule::CART_FIXED_ACTION], + ['label' => __('Buy X get Y free (discount amount is Y)'), 'value' => Rule::BUY_X_GET_Y_ACTION] + ]; + + $this->assertEquals($expected, $this->model->toOptionArray()); + } +} diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Metadata/ValueProviderTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Metadata/ValueProviderTest.php index 0864b4a5e1480..d63ba150f4822 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Metadata/ValueProviderTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Metadata/ValueProviderTest.php @@ -5,52 +5,72 @@ */ namespace Magento\SalesRule\Test\Unit\Model\Rule\Metadata; +use Magento\Customer\Api\Data\GroupInterface; +use Magento\Customer\Api\Data\GroupSearchResultsInterface; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Framework\Convert\DataObject; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\SalesRule\Model\Rule; +use Magento\SalesRule\Model\Rule\Action\SimpleActionOptionsProvider; +use Magento\SalesRule\Model\Rule\Metadata\ValueProvider; +use Magento\SalesRule\Model\RuleFactory; +use Magento\Store\Model\System\Store; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; /** * @covers Magento\SalesRule\Model\Rule\Metadata\ValueProvider */ -class ValueProviderTest extends \PHPUnit\Framework\TestCase +class ValueProviderTest extends TestCase { /** - * @var \Magento\SalesRule\Model\Rule\Metadata\ValueProvider + * @var ValueProvider */ protected $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Store|MockObject */ protected $storeMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var GroupRepositoryInterface|MockObject */ protected $groupRepositoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var SearchCriteriaBuilder|MockObject */ protected $searchCriteriaBuilderMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var DataObject|MockObject */ protected $dataObjectMock; /** - * @var \Magento\SalesRule\Model\RuleFactory|\PHPUnit_Framework_MockObject_MockObject + * @var RuleFactory|MockObject */ protected $ruleFactoryMock; + /** + * @var SimpleActionOptionsProvider|MockObject + */ + private $simpleActionOptionsProviderMock; + protected function setUp() { - $this->searchCriteriaBuilderMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaBuilder::class); - $this->storeMock = $this->createMock(\Magento\Store\Model\System\Store::class); - $this->groupRepositoryMock = $this->createMock(\Magento\Customer\Api\GroupRepositoryInterface::class); - $this->dataObjectMock = $this->createMock(\Magento\Framework\Convert\DataObject::class); - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class); - $groupSearchResultsMock = $this->createMock(\Magento\Customer\Api\Data\GroupSearchResultsInterface::class); - $groupsMock = $this->createMock(\Magento\Customer\Api\Data\GroupInterface::class); + $expectedData = include __DIR__ . '/_files/MetaData.php'; + $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); + $this->storeMock = $this->createMock(Store::class); + $this->groupRepositoryMock = $this->createMock(GroupRepositoryInterface::class); + $this->dataObjectMock = $this->createMock(DataObject::class); + $this->simpleActionOptionsProviderMock = $this->createMock(SimpleActionOptionsProvider::class); + $searchCriteriaMock = $this->createMock(SearchCriteriaInterface::class); + $groupSearchResultsMock = $this->createMock(GroupSearchResultsInterface::class); + $groupsMock = $this->createMock(GroupInterface::class); $this->searchCriteriaBuilderMock->expects($this->once())->method('create')->willReturn($searchCriteriaMock); $this->groupRepositoryMock->expects($this->once())->method('getList')->with($searchCriteriaMock) @@ -59,15 +79,19 @@ protected function setUp() $this->storeMock->expects($this->once())->method('getWebsiteValuesForForm')->willReturn([]); $this->dataObjectMock->expects($this->once())->method('toOptionArray')->with([$groupsMock], 'id', 'code') ->willReturn([]); - $this->ruleFactoryMock = $this->createPartialMock(\Magento\SalesRule\Model\RuleFactory::class, ['create']); + $this->ruleFactoryMock = $this->createPartialMock(RuleFactory::class, ['create']); + $this->simpleActionOptionsProviderMock->method('toOptionArray')->willReturn( + $expectedData['actions']['children']['simple_action']['arguments']['data']['config']['options'] + ); $this->model = (new ObjectManager($this))->getObject( - \Magento\SalesRule\Model\Rule\Metadata\ValueProvider::class, + ValueProvider::class, [ 'store' => $this->storeMock, 'groupRepository' => $this->groupRepositoryMock, 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, 'objectConverter' => $this->dataObjectMock, 'salesRuleFactory' => $this->ruleFactoryMock, + 'simpleActionOptionsProvider' => $this->simpleActionOptionsProviderMock ] ); } @@ -76,8 +100,8 @@ public function testGetMetadataValues() { $expectedData = include __DIR__ . '/_files/MetaData.php'; - /** @var \Magento\SalesRule\Model\Rule|\PHPUnit_Framework_MockObject_MockObject $ruleMock */ - $ruleMock = $this->createMock(\Magento\SalesRule\Model\Rule::class); + /** @var Rule|MockObject $ruleMock */ + $ruleMock = $this->createMock(Rule::class); $this->ruleFactoryMock->expects($this->once()) ->method('create') ->willReturn($ruleMock); diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js index 033e2e43a3c22..aca843872af65 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js @@ -79,7 +79,9 @@ define([ $(element).is('textarea') || $('#' + element.id + ' option:selected').length ) { - dataToAdd = $.extend({}, dataToAdd, self._getElementData(element)); + if ($(element).data('selector') || $(element).attr('name')) { + dataToAdd = $.extend({}, dataToAdd, self._getElementData(element)); + } return; } diff --git a/lib/internal/Magento/Framework/App/ProductMetadata.php b/lib/internal/Magento/Framework/App/ProductMetadata.php index c9fde94352a71..631dba8273bcd 100644 --- a/lib/internal/Magento/Framework/App/ProductMetadata.php +++ b/lib/internal/Magento/Framework/App/ProductMetadata.php @@ -8,12 +8,13 @@ namespace Magento\Framework\App; use Magento\Framework\Composer\ComposerFactory; -use \Magento\Framework\Composer\ComposerJsonFinder; -use \Magento\Framework\App\Filesystem\DirectoryList; -use \Magento\Framework\Composer\ComposerInformation; +use Magento\Framework\Composer\ComposerJsonFinder; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Composer\ComposerInformation; /** * Class ProductMetadata + * * @package Magento\Framework\App */ class ProductMetadata implements ProductMetadataInterface @@ -28,6 +29,11 @@ class ProductMetadata implements ProductMetadataInterface */ const PRODUCT_NAME = 'Magento'; + /** + * Cache key for Magento product version + */ + private const MAGENTO_PRODUCT_VERSION_CACHE_KEY = 'magento-product-version'; + /** * Product version * @@ -46,12 +52,19 @@ class ProductMetadata implements ProductMetadataInterface */ private $composerInformation; + /** + * @var CacheInterface + */ + private $cache; + /** * @param ComposerJsonFinder $composerJsonFinder + * @param CacheInterface|null $cache */ - public function __construct(ComposerJsonFinder $composerJsonFinder) + public function __construct(ComposerJsonFinder $composerJsonFinder, CacheInterface $cache = null) { $this->composerJsonFinder = $composerJsonFinder; + $this->cache = $cache ?: ObjectManager::getInstance()->get(CacheInterface::class); } /** @@ -61,6 +74,9 @@ public function __construct(ComposerJsonFinder $composerJsonFinder) */ public function getVersion() { + if ($cachedVersion = $this->cache->load(self::MAGENTO_PRODUCT_VERSION_CACHE_KEY)) { + $this->version = $cachedVersion; + } if (!$this->version) { if (!($this->version = $this->getSystemPackageVersion())) { if ($this->getComposerInformation()->isMagentoRoot()) { @@ -69,6 +85,7 @@ public function getVersion() $this->version = 'UNKNOWN'; } } + $this->cache->save($this->version, self::MAGENTO_PRODUCT_VERSION_CACHE_KEY); } return $this->version; } diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 55921c054e61a..2ec39efcc283e 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -559,7 +559,7 @@ /* eslint-enable max-len */ 'pattern': [ function (value, element, param) { - return this.optional(element) || param.test(value); + return this.optional(element) || new RegExp(param).test(value); }, $.mage.__('Invalid format.') ],