diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml index 889517e629e0..4e21648d00ce 100644 --- a/app/code/Magento/Analytics/etc/adminhtml/system.xml +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -17,14 +17,14 @@ Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the "Go to Advanced Reporting" link.
For more information, see our terms and conditions.]]> - + Magento\Config\Model\Config\Source\Enabledisable Magento\Analytics\Model\Config\Backend\Enabled Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel analytics/subscription/enabled - + Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel Magento\Analytics\Model\Config\Backend\CollectionTime diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js index 03403a4ec4a0..f9f43cebc592 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js @@ -13,11 +13,19 @@ define([ 'jquery', 'mage/template', 'Magento_Ui/js/modal/alert', + 'Magento_Ui/js/form/element/file-uploader', 'mage/translate', 'jquery/file-uploader' -], function ($, mageTemplate, alert) { +], function ($, mageTemplate, alert, FileUploader) { 'use strict'; + var fileUploader = new FileUploader({ + dataScope: '', + isMultipleFiles: true + }); + + fileUploader.initUploader(); + $.widget('mage.mediaUploader', { /** @@ -79,10 +87,9 @@ define([ if (data.result && !data.result.error) { self.element.trigger('addItem', data.result); } else { - alert({ - content: $.mage.__('We don\'t recognize or support this file extension type.') - }); + fileUploader.aggregateError(data.files[0].name, data.result.error); } + self.element.find('#' + data.fileId).remove(); }, @@ -108,7 +115,9 @@ define([ .delay(2000) .hide('highlight') .remove(); - } + }, + + stop: fileUploader.uploaderConfig.stop }); this.element.find('input[type=file]').fileupload('option', { diff --git a/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php new file mode 100644 index 000000000000..167fcb1569cb --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeProvider.php @@ -0,0 +1,43 @@ +errors; + + /** @var Validation $error */ + foreach ($collection->deepAll() as $error) { + $result[] = $error->code; + } + + return $result; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php b/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php index 8028bace0cf2..6aac588c3837 100644 --- a/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php +++ b/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php @@ -18,16 +18,26 @@ class GeneralResponseValidator extends AbstractValidator */ protected $subjectReader; + /** + * @var ErrorCodeProvider + */ + private $errorCodeProvider; + /** * Constructor * * @param ResultInterfaceFactory $resultFactory * @param SubjectReader $subjectReader + * @param ErrorCodeProvider $errorCodeProvider */ - public function __construct(ResultInterfaceFactory $resultFactory, SubjectReader $subjectReader) - { + public function __construct( + ResultInterfaceFactory $resultFactory, + SubjectReader $subjectReader, + ErrorCodeProvider $errorCodeProvider + ) { parent::__construct($resultFactory); $this->subjectReader = $subjectReader; + $this->errorCodeProvider = $errorCodeProvider; } /** @@ -49,8 +59,9 @@ public function validate(array $validationSubject) $errorMessages = array_merge($errorMessages, $validationResult[1]); } } + $errorCodes = $this->errorCodeProvider->getErrorCodes($response); - return $this->createResult($isValid, $errorMessages); + return $this->createResult($isValid, $errorMessages, $errorCodes); } /** @@ -62,7 +73,7 @@ protected function getResponseValidators() function ($response) { return [ property_exists($response, 'success') && $response->success === true, - [__('Braintree error response.')] + [$response->message ?? __('Braintree error response.')] ]; } ]; diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php index bf15d038297b..23167af02a97 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Command/GetPaymentNonceCommandTest.php @@ -99,7 +99,7 @@ protected function setUp() ->getMock(); $this->validationResultMock = $this->getMockBuilder(ResultInterface::class) - ->setMethods(['isValid', 'getFailsDescription']) + ->setMethods(['isValid', 'getFailsDescription', 'getErrorCodes']) ->getMock(); $this->responseValidatorMock = $this->getMockBuilder(PaymentNonceResponseValidator::class) diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php index c8a46da504fe..4741a3ea38c6 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php @@ -5,12 +5,14 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; -use Braintree\Transaction; +use Braintree\Result\Error; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; +use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; use Magento\Framework\Phrase; -use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; -use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase { @@ -20,14 +22,9 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase private $responseValidator; /** - * @var ResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $resultInterfaceFactoryMock; - - /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterfaceFactory|MockObject */ - private $subjectReaderMock; + private $resultInterfaceFactory; /** * Set up @@ -36,85 +33,105 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->resultInterfaceFactoryMock = $this->getMockBuilder( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory::class - )->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) + $this->resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class) ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); $this->responseValidator = new GeneralResponseValidator( - $this->resultInterfaceFactoryMock, - $this->subjectReaderMock + $this->resultInterfaceFactory, + new SubjectReader(), + new ErrorCodeProvider() ); } /** - * Run test for validate method + * Checks a case when the validator processes successful and failed transactions. * * @param array $validationSubject * @param bool $isValid * @param Phrase[] $messages + * @param array $errorCodes * @return void * * @dataProvider dataProviderTestValidate */ - public function testValidate(array $validationSubject, $isValid, $messages) + public function testValidate(array $validationSubject, bool $isValid, $messages, array $errorCodes) { - /** @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject $resultMock */ - $resultMock = $this->createMock(ResultInterface::class); - - $this->subjectReaderMock->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willReturn($validationSubject['response']['object']); + $result = new Result($isValid, $messages); - $this->resultInterfaceFactoryMock->expects(self::once()) - ->method('create') + $this->resultInterfaceFactory->method('create') ->with([ 'isValid' => $isValid, - 'failsDescription' => $messages + 'failsDescription' => $messages, + 'errorCodes' => $errorCodes ]) - ->willReturn($resultMock); + ->willReturn($result); - $actualMock = $this->responseValidator->validate($validationSubject); + $actual = $this->responseValidator->validate($validationSubject); - self::assertEquals($resultMock, $actualMock); + self::assertEquals($result, $actual); } /** + * Gets variations for different type of response. + * * @return array */ public function dataProviderTestValidate() { - $successTrue = new \stdClass(); - $successTrue->success = true; + $successTransaction = new \stdClass(); + $successTransaction->success = true; + + $failureTransaction = new \stdClass(); + $failureTransaction->success = false; + $failureTransaction->message = 'Transaction was failed.'; - $successFalse = new \stdClass(); - $successFalse->success = false; + $errors = [ + 'errors' => [ + [ + 'code' => 81804, + 'attribute' => 'base', + 'message' => 'Cannot process transaction.' + ] + ] + ]; + $errorTransaction = new Error(['errors' => $errors]); return [ [ 'validationSubject' => [ 'response' => [ - 'object' => $successTrue + 'object' => $successTransaction ], ], 'isValid' => true, - [] + [], + 'errorCodes' => [] + ], + [ + 'validationSubject' => [ + 'response' => [ + 'object' => $failureTransaction + ] + ], + 'isValid' => false, + [ + __('Transaction was failed.') + ], + 'errorCodes' => [] ], [ 'validationSubject' => [ 'response' => [ - 'object' => $successFalse + 'object' => $errorTransaction ] ], 'isValid' => false, [ __('Braintree error response.') - ] + ], + 'errorCodes' => ['81804'] ] ]; } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php index 03363b5463d7..530945c974ce 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php @@ -5,15 +5,13 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; -use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; use Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator; -use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; -/** - * Class PaymentNonceResponseValidatorTest - */ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase { /** @@ -22,35 +20,24 @@ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase private $validator; /** - * @var ResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterfaceFactory|MockObject */ private $resultInterfaceFactory; - /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject - */ - private $subjectReader; - protected function setUp() { $this->resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->setMethods(['readResponseObject']) - ->getMock(); $this->validator = new PaymentNonceResponseValidator( $this->resultInterfaceFactory, - $this->subjectReader + new SubjectReader(), + new ErrorCodeProvider() ); } - /** - * @covers \Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator::validate - */ public function testFailedValidate() { $obj = new \stdClass(); @@ -61,23 +48,12 @@ public function testFailedValidate() ] ]; - $this->subjectReader->expects(static::once()) - ->method('readResponseObject') - ->willReturn($obj); - - $result = $this->createMock(ResultInterface::class); - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => false, - 'failsDescription' => [ - __('Payment method nonce can\'t be retrieved.') - ] - ]) + $result = new Result(false, [__('Payment method nonce can\'t be retrieved.')]); + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->validator->validate($subject); - static::assertEquals($result, $actual); + self::assertEquals($result, $actual); } public function testValidateSuccess() @@ -93,20 +69,11 @@ public function testValidateSuccess() ] ]; - $this->subjectReader->expects(static::once()) - ->method('readResponseObject') - ->willReturn($obj); - - $result = $this->createMock(ResultInterface::class); - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => true, - 'failsDescription' => [] - ]) + $result = new Result(true); + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->validator->validate($subject); - static::assertEquals($result, $actual); + self::assertEquals($result, $actual); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php index 4bd446079f9a..360e1ff0525b 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php @@ -5,15 +5,16 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; +use Braintree\Result\Successful; use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeProvider; +use Magento\Braintree\Gateway\Validator\ResponseValidator; use Magento\Framework\Phrase; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Validator\ResponseValidator; -use Magento\Braintree\Gateway\SubjectReader; use PHPUnit_Framework_MockObject_MockObject as MockObject; -use Braintree\Result\Error; -use Braintree\Result\Successful; /** * Class ResponseValidatorTest @@ -30,11 +31,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase */ private $resultInterfaceFactory; - /** - * @var SubjectReader|MockObject - */ - private $subjectReader; - /** * Set up * @@ -46,13 +42,11 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->getMock(); $this->responseValidator = new ResponseValidator( $this->resultInterfaceFactory, - $this->subjectReader + new SubjectReader(), + new ErrorCodeProvider() ); } @@ -65,11 +59,6 @@ public function testValidateReadResponseException() 'response' => null ]; - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willThrowException(new \InvalidArgumentException()); - $this->responseValidator->validate($validationSubject); } @@ -82,11 +71,6 @@ public function testValidateReadResponseObjectException() 'response' => ['object' => null] ]; - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willThrowException(new \InvalidArgumentException()); - $this->responseValidator->validate($validationSubject); } @@ -103,19 +87,9 @@ public function testValidateReadResponseObjectException() public function testValidate(array $validationSubject, $isValid, $messages) { /** @var ResultInterface|MockObject $result */ - $result = $this->createMock(ResultInterface::class); - - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willReturn($validationSubject['response']['object']); - - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => $isValid, - 'failsDescription' => $messages - ]) + $result = new Result($isValid, $messages); + + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->responseValidator->validate($validationSubject); @@ -141,8 +115,6 @@ public function dataProviderTestValidate() $transactionDeclined->transaction = new \stdClass(); $transactionDeclined->transaction->status = Transaction::SETTLEMENT_DECLINED; - $errorResult = new Error(['errors' => []]); - return [ [ 'validationSubject' => [ @@ -175,18 +147,6 @@ public function dataProviderTestValidate() [ __('Wrong transaction status') ] - ], - [ - 'validationSubject' => [ - 'response' => [ - 'object' => $errorResult, - ] - ], - 'isValid' => false, - [ - __('Braintree error response.'), - __('Wrong transaction status') - ] ] ]; } diff --git a/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml new file mode 100644 index 000000000000..611f9372518f --- /dev/null +++ b/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml @@ -0,0 +1,32 @@ + + + + + Credit card type is not accepted by this merchant account. + Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending. + Credit transactions cannot be refunded. + Cannot refund a transaction unless it is settled. + Cannot submit for settlement unless status is authorized. + Customer does not have any credit cards. + Transaction has already been completely refunded. + Payment instrument type is not accepted by this merchant account. + Processor authorization code cannot be set unless for a voice authorization. + Refund amount is too large. + Settlement amount is too large. + Cannot provide a billing address unless also providing a credit card. + Cannot refund a transaction with a suspended merchant account. + Merchant account does not support refunds. + Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled. + PayPal is not enabled for your merchant account. + Partial settlements are not supported by this processor. + Cannot submit for partial settlement. + Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending. + Too many concurrent attempts to refund this transaction. Try again later. + Too many concurrent attempts to void this transaction. Try again later. + + diff --git a/app/code/Magento/Braintree/etc/adminhtml/di.xml b/app/code/Magento/Braintree/etc/adminhtml/di.xml index b7231f54186b..7a803f803ae8 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/di.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml @@ -45,6 +45,19 @@ + + + + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper + + + + + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper + + + + diff --git a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml new file mode 100644 index 000000000000..81da0a252e56 --- /dev/null +++ b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml @@ -0,0 +1,64 @@ + + + + + Credit card type is not accepted by this merchant account. + CVV is required. + CVV must be 4 digits for American Express and 3 digits for other card types. + Expiration date is required. + Expiration date is invalid. + Expiration year is invalid. It must be between 1975 and 2201. + Expiration month is invalid. + Expiration year is invalid. + Credit card number is required. + Credit card number is invalid. + Credit card number must be 12-19 digits. + Cardholder name is too long. + CVV verification failed. + Postal code verification failed. + Credit card number is prohibited. + Addresses must have at least one field filled in. + Company is too long. + Extended address is too long. + First name is too long. + Last name is too long. + Locality is too long. + Postal code is required. + Postal code may contain no more than 9 letter or number characters. + Region is too long. + Street address is required. + Street address is too long. + Postal code can only contain letters, numbers, spaces, and hyphens. + US state codes must be two characters to meet PayPal Seller Protection requirements. + Incomplete PayPal account information. + Invalid PayPal account information. + PayPal Accounts are not accepted by this merchant account. + Credit card type is not accepted by this merchant account. + Credit card type is not accepted by this merchant account. + Billing address format is invalid. + Country name is not an accepted country. + Country code is not accepted. Please contact the store administrator. + Provided country information is inconsistent. + Country code is not accepted. Please contact the store administrator. + Country code is not accepted. Please contact the store administrator. + Customer has already reached the maximum of 50 addresses. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Address is invalid. Please contact the store administrator. + Error communicating with PayPal. + PayPal authentication expired. + Error executing PayPal order. + Error executing PayPal billing agreement. + + diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 1b839f469a14..5f4a345760f2 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -193,6 +193,23 @@ + + + braintree_error_mapping.xml + + + + + Magento\Braintree\Gateway\ErrorMapper\VirtualConfigReader + braintree_error_mapper + + + + + Magento\Braintree\Gateway\ErrorMapper\VirtualMappingData + + + @@ -201,6 +218,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSale BraintreeAuthorizationHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper @@ -240,6 +258,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSubmitForSettlement Magento\Braintree\Gateway\Response\TransactionIdHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper @@ -258,6 +277,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSale BraintreeVaultResponseHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper @@ -296,6 +316,7 @@ Magento\Braintree\Gateway\Http\Client\TransactionSale Magento\Braintree\Gateway\Response\TransactionIdHandler Magento\Braintree\Gateway\Validator\ResponseValidator + Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv index 116f459a1c1c..194ad14d4975 100644 --- a/app/code/Magento/Braintree/i18n/en_US.csv +++ b/app/code/Magento/Braintree/i18n/en_US.csv @@ -129,3 +129,65 @@ Amount,Amount "Refund Ids","Refund Ids" "Settlement Batch ID","Settlement Batch ID" Currency,Currency +"Addresses must have at least one field filled in.","Addresses must have at least one field filled in." +"Company is too long.","Company is too long." +"Extended address is too long.","Extended address is too long." +"First name is too long.","First name is too long." +"Last name is too long.","Last name is too long." +"Locality is too long.","Locality is too long." +"Postal code can only contain letters, numbers, spaces, and hyphens.","Postal code can only contain letters, numbers, spaces, and hyphens." +"Postal code is required.","Postal code is required." +"Postal code may contain no more than 9 letter or number characters.","Postal code may contain no more than 9 letter or number characters." +"Region is too long.","Region is too long." +"Street address is required.","Street address is required." +"Street address is too long.","Street address is too long." +"US state codes must be two characters to meet PayPal Seller Protection requirements.","US state codes must be two characters to meet PayPal Seller Protection requirements." +"Country name is not an accepted country.","Country name is not an accepted country." +"Provided country information is inconsistent.","Provided country information is inconsistent." +"Country code is not accepted. Please contact the store administrator.","Country code is not accepted. Please contact the store administrator." +"Customer has already reached the maximum of 50 addresses.","Customer has already reached the maximum of 50 addresses." +"Address is invalid. Please contact the store administrator.","Address is invalid. Please contact the store administrator." +"Address is invalid.","Address is invalid." +"Billing address format is invalid.","Billing address format is invalid." +"Cardholder name is too long.","Cardholder name is too long." +"Credit card type is not accepted by this merchant account.","Credit card type is not accepted by this merchant account." +"CVV is required.","CVV is required." +"CVV must be 4 digits for American Express and 3 digits for other card types.","CVV must be 4 digits for American Express and 3 digits for other card types." +"Expiration date is required.","Expiration date is required." +"Expiration date is invalid.","Expiration date is invalid." +"Expiration year is invalid. It must be between 1975 and 2201.","Expiration year is invalid. It must be between 1975 and 2201." +"Expiration month is invalid.","Expiration month is invalid." +"Expiration year is invalid.","Expiration year is invalid." +"Credit card number is required.","Credit card number is required." +"Credit card number is invalid.","Credit card number is invalid." +"Credit card number must be 12-19 digits.","Credit card number must be 12-19 digits." +"CVV verification failed.","CVV verification failed." +"Postal code verification failed.","Postal code verification failed." +"Credit card number is prohibited.","Credit card number is prohibited." +"Incomplete PayPal account information.","Incomplete PayPal account information." +"Invalid PayPal account information.","Invalid PayPal account information." +"PayPal Accounts are not accepted by this merchant account.","PayPal Accounts are not accepted by this merchant account." +"Error communicating with PayPal.","Error communicating with PayPal." +"PayPal authentication expired.","PayPal authentication expired." +"Error executing PayPal order.","Error executing PayPal order." +"Error executing PayPal billing agreement.","Error executing PayPal billing agreement." +"Cannot provide a billing address unless also providing a credit card.","Cannot provide a billing address unless also providing a credit card." +"Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending.","Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending." +"Credit transactions cannot be refunded.","Credit transactions cannot be refunded." +"Cannot refund a transaction unless it is settled.","Cannot refund a transaction unless it is settled." +"Cannot submit for settlement unless status is authorized.","Cannot submit for settlement unless status is authorized." +"Customer does not have any credit cards.","Customer does not have any credit cards." +"Transaction has already been completely refunded.","Transaction has already been completely refunded." +"Payment instrument type is not accepted by this merchant account.","Payment instrument type is not accepted by this merchant account." +"Processor authorization code cannot be set unless for a voice authorization.","Processor authorization code cannot be set unless for a voice authorization." +"Refund amount is too large.","Refund amount is too large." +"Settlement amount is too large.","Settlement amount is too large." +"Cannot refund a transaction with a suspended merchant account.","Cannot refund a transaction with a suspended merchant account." +"Merchant account does not support refunds.","Merchant account does not support refunds." +"PayPal is not enabled for your merchant account.","PayPal is not enabled for your merchant account." +"Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled.","Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled." +"Cannot submit for partial settlement.","Cannot submit for partial settlement." +"Partial settlements are not supported by this processor.","Partial settlements are not supported by this processor." +"Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending.","Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending." +"Too many concurrent attempts to refund this transaction. Try again later.","Too many concurrent attempts to refund this transaction. Try again later." +"Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later." \ No newline at end of file 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 6cb103fc8678..542f170da8c3 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 @@ -217,10 +217,11 @@ public function getOptionHtml(Option $option) } /** - * Get formed data from option selection item + * Get formed data from option selection item. * * @param Product $product * @param Product $selection + * * @return array */ private function getSelectionItemData(Product $product, Product $selection) @@ -228,31 +229,37 @@ private function getSelectionItemData(Product $product, Product $selection) $qty = ($selection->getSelectionQty() * 1) ?: '1'; $optionPriceAmount = $product->getPriceInfo() - ->getPrice('bundle_option') + ->getPrice(\Magento\Bundle\Pricing\Price\BundleOptionPrice::PRICE_CODE) ->getOptionSelectionAmount($selection); $finalPrice = $optionPriceAmount->getValue(); $basePrice = $optionPriceAmount->getBaseAmount(); + $oldPrice = $product->getPriceInfo() + ->getPrice(\Magento\Bundle\Pricing\Price\BundleOptionRegularPrice::PRICE_CODE) + ->getOptionSelectionAmount($selection) + ->getValue(); + $selection = [ 'qty' => $qty, 'customQty' => $selection->getSelectionCanChangeQty(), 'optionId' => $selection->getId(), 'prices' => [ 'oldPrice' => [ - 'amount' => $basePrice + 'amount' => $oldPrice, ], 'basePrice' => [ - 'amount' => $basePrice + 'amount' => $basePrice, ], 'finalPrice' => [ - 'amount' => $finalPrice - ] + 'amount' => $finalPrice, + ], ], 'priceType' => $selection->getSelectionPriceType(), 'tierPrice' => $this->getTierPrices($product, $selection), 'name' => $selection->getName(), - 'canApplyMsrp' => false + 'canApplyMsrp' => false, ]; + return $selection; } diff --git a/app/code/Magento/Bundle/Pricing/Price/BundleOptionPrice.php b/app/code/Magento/Bundle/Pricing/Price/BundleOptionPrice.php index 995572636e75..1c724caaa28d 100644 --- a/app/code/Magento/Bundle/Pricing/Price/BundleOptionPrice.php +++ b/app/code/Magento/Bundle/Pricing/Price/BundleOptionPrice.php @@ -8,9 +8,10 @@ use Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface; use Magento\Catalog\Model\Product; use Magento\Framework\Pricing\Price\AbstractPrice; +use Magento\Framework\App\ObjectManager; /** - * Bundle option price model + * Bundle option price model with final price. */ class BundleOptionPrice extends AbstractPrice implements BundleOptionPriceInterface { @@ -26,6 +27,7 @@ class BundleOptionPrice extends AbstractPrice implements BundleOptionPriceInterf /** * @var BundleSelectionFactory + * @deprecated */ protected $selectionFactory; @@ -34,23 +36,31 @@ class BundleOptionPrice extends AbstractPrice implements BundleOptionPriceInterf */ protected $maximalPrice; + /** + * @var BundleOptions + */ + private $bundleOptions; + /** * @param Product $saleableItem * @param float $quantity * @param BundleCalculatorInterface $calculator * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency * @param BundleSelectionFactory $bundleSelectionFactory + * @param BundleOptions|null $bundleOptions */ public function __construct( Product $saleableItem, $quantity, BundleCalculatorInterface $calculator, \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, - BundleSelectionFactory $bundleSelectionFactory + BundleSelectionFactory $bundleSelectionFactory, + BundleOptions $bundleOptions = null ) { $this->selectionFactory = $bundleSelectionFactory; parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); $this->product->setQty($this->quantity); + $this->bundleOptions = $bundleOptions ?: ObjectManager::getInstance()->get(BundleOptions::class); } /** @@ -59,94 +69,61 @@ public function __construct( public function getValue() { if (null === $this->value) { - $this->value = $this->calculateOptions(); + $this->value = $this->bundleOptions->calculateOptions($this->product); } + return $this->value; } /** - * Getter for maximal price of options + * Getter for maximal price of options. * * @return bool|float + * @deprecated */ public function getMaxValue() { if (null === $this->maximalPrice) { - $this->maximalPrice = $this->calculateOptions(false); + $this->maximalPrice = $this->bundleOptions->calculateOptions($this->product, false); } + return $this->maximalPrice; } /** - * Get Options with attached Selections collection + * Get Options with attached Selections collection. * * @return \Magento\Bundle\Model\ResourceModel\Option\Collection */ public function getOptions() { - $bundleProduct = $this->product; - /** @var \Magento\Bundle\Model\Product\Type $typeInstance */ - $typeInstance = $bundleProduct->getTypeInstance(); - $typeInstance->setStoreFilter($bundleProduct->getStoreId(), $bundleProduct); - - /** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionCollection */ - $optionCollection = $typeInstance->getOptionsCollection($bundleProduct); - - $selectionCollection = $typeInstance->getSelectionsCollection( - $typeInstance->getOptionsIds($bundleProduct), - $bundleProduct - ); - - $priceOptions = $optionCollection->appendSelections($selectionCollection, true, false); - return $priceOptions; + return $this->bundleOptions->getOptions($this->product); } /** - * Get selection amount + * Get selection amount. * * @param \Magento\Bundle\Model\Selection $selection * @return \Magento\Framework\Pricing\Amount\AmountInterface */ public function getOptionSelectionAmount($selection) { - $cacheKey = implode( - '_', - [ - $this->product->getId(), - $selection->getOptionId(), - $selection->getSelectionId() - ] + return $this->bundleOptions->getOptionSelectionAmount( + $this->product, + $selection, + false ); - - if (!isset($this->optionSelecionAmountCache[$cacheKey])) { - $selectionPrice = $this->selectionFactory - ->create($this->product, $selection, $selection->getSelectionQty()); - $this->optionSelecionAmountCache[$cacheKey] = $selectionPrice->getAmount(); - } - - return $this->optionSelecionAmountCache[$cacheKey]; } /** - * Calculate maximal or minimal options value + * Calculate maximal or minimal options value. * * @param bool $searchMin * @return bool|float */ protected function calculateOptions($searchMin = true) { - $priceList = []; - /* @var $option \Magento\Bundle\Model\Option */ - foreach ($this->getOptions() as $option) { - if ($searchMin && !$option->getRequired()) { - continue; - } - $selectionPriceList = $this->calculator->createSelectionPriceList($option, $this->product); - $selectionPriceList = $this->calculator->processOptions($option, $selectionPriceList, $searchMin); - $priceList = array_merge($priceList, $selectionPriceList); - } - $amount = $this->calculator->calculateBundleAmount(0., $this->product, $priceList); - return $amount->getValue(); + return $this->bundleOptions->calculateOptions($this->product, $searchMin); } /** diff --git a/app/code/Magento/Bundle/Pricing/Price/BundleOptionRegularPrice.php b/app/code/Magento/Bundle/Pricing/Price/BundleOptionRegularPrice.php new file mode 100644 index 000000000000..20262e99281d --- /dev/null +++ b/app/code/Magento/Bundle/Pricing/Price/BundleOptionRegularPrice.php @@ -0,0 +1,100 @@ +product->setQty($this->quantity); + $this->bundleOptions = $bundleOptions; + } + + /** + * {@inheritdoc} + */ + public function getValue() + { + if (null === $this->value) { + $this->value = $this->bundleOptions->calculateOptions($this->product); + } + + return $this->value; + } + + /** + * Get Options with attached Selections collection. + * + * @return \Magento\Bundle\Model\ResourceModel\Option\Collection + */ + public function getOptions() : \Magento\Bundle\Model\ResourceModel\Option\Collection + { + return $this->bundleOptions->getOptions($this->product); + } + + /** + * Get selection amount. + * + * @param \Magento\Bundle\Model\Selection $selection + * @return \Magento\Framework\Pricing\Amount\AmountInterface + */ + public function getOptionSelectionAmount($selection) : \Magento\Framework\Pricing\Amount\AmountInterface + { + return $this->bundleOptions->getOptionSelectionAmount( + $this->product, + $selection, + true + ); + } + + /** + * Get minimal amount of bundle price with options. + * + * @return \Magento\Framework\Pricing\Amount\AmountInterface + */ + public function getAmount() : \Magento\Framework\Pricing\Amount\AmountInterface + { + return $this->calculator->getOptionsAmount($this->product); + } +} diff --git a/app/code/Magento/Bundle/Pricing/Price/BundleOptions.php b/app/code/Magento/Bundle/Pricing/Price/BundleOptions.php new file mode 100644 index 000000000000..e4951cc31173 --- /dev/null +++ b/app/code/Magento/Bundle/Pricing/Price/BundleOptions.php @@ -0,0 +1,138 @@ +calculator = $calculator; + $this->selectionFactory = $bundleSelectionFactory; + } + + /** + * Get Options with attached Selections collection. + * + * @param SaleableInterface $bundleProduct + * @return \Magento\Bundle\Model\ResourceModel\Option\Collection|array + */ + public function getOptions(SaleableInterface $bundleProduct) + { + /** @var \Magento\Bundle\Model\Product\Type $typeInstance */ + $typeInstance = $bundleProduct->getTypeInstance(); + $typeInstance->setStoreFilter($bundleProduct->getStoreId(), $bundleProduct); + + /** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionCollection */ + $optionCollection = $typeInstance->getOptionsCollection($bundleProduct); + + /** @var \Magento\Bundle\Model\ResourceModel\Selection\Collection $selectionCollection */ + $selectionCollection = $typeInstance->getSelectionsCollection( + $typeInstance->getOptionsIds($bundleProduct), + $bundleProduct + ); + + $priceOptions = $optionCollection->appendSelections($selectionCollection, true, false); + + return $priceOptions; + } + + /** + * Calculate maximal or minimal options value. + * + * @param SaleableInterface $bundleProduct + * @param bool $searchMin + * + * @return float + */ + public function calculateOptions( + SaleableInterface $bundleProduct, + bool $searchMin = true + ) : float { + $priceList = []; + /* @var \Magento\Bundle\Model\Option $option */ + foreach ($this->getOptions($bundleProduct) as $option) { + if ($searchMin && !$option->getRequired()) { + continue; + } + /** @var \Magento\Bundle\Pricing\Price\BundleSelectionPrice $selectionPriceList */ + $selectionPriceList = $this->calculator->createSelectionPriceList($option, $bundleProduct); + $selectionPriceList = $this->calculator->processOptions($option, $selectionPriceList, $searchMin); + $priceList = array_merge($priceList, $selectionPriceList); + } + $amount = $this->calculator->calculateBundleAmount(0., $bundleProduct, $priceList); + + return $amount->getValue(); + } + + /** + * Get selection amount. + * + * @param Product $bundleProduct + * @param \Magento\Bundle\Model\Selection|Product $selection + * @param bool $useRegularPrice + * + * @return AmountInterface + */ + public function getOptionSelectionAmount( + Product $bundleProduct, + $selection, + bool $useRegularPrice = false + ) : AmountInterface { + $cacheKey = implode( + '_', + [ + $bundleProduct->getId(), + $selection->getOptionId(), + $selection->getSelectionId(), + $useRegularPrice ? 1 : 0, + ] + ); + + if (!isset($this->optionSelectionAmountCache[$cacheKey])) { + $selectionPrice = $this->selectionFactory + ->create( + $bundleProduct, + $selection, + $selection->getSelectionQty(), + ['useRegularPrice' => $useRegularPrice] + ); + $this->optionSelectionAmountCache[$cacheKey] = $selectionPrice->getAmount(); + } + + return $this->optionSelectionAmountCache[$cacheKey]; + } +} diff --git a/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php b/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php index 6982e2a77e9f..db2b9547e635 100644 --- a/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php +++ b/app/code/Magento/Bundle/Pricing/Price/BundleSelectionPrice.php @@ -93,7 +93,7 @@ public function __construct( } /** - * Get the price value for one of selection product + * Get the price value for one of selection product. * * @return bool|float */ @@ -103,7 +103,10 @@ public function getValue() return $this->value; } $product = $this->selection; - $bundleSelectionKey = 'bundle-selection-value-' . $product->getSelectionId(); + $bundleSelectionKey = 'bundle-selection-' + . ($this->useRegularPrice ? 'regular-' : '') + . 'value-' + . $product->getSelectionId(); if ($product->hasData($bundleSelectionKey)) { return $product->getData($bundleSelectionKey); } @@ -140,6 +143,7 @@ public function getValue() } $this->value = $this->priceCurrency->round($value); $product->setData($bundleSelectionKey, $this->value); + return $this->value; } @@ -151,7 +155,10 @@ public function getValue() public function getAmount() { $product = $this->selection; - $bundleSelectionKey = 'bundle-selection-amount-' . $product->getSelectionId(); + $bundleSelectionKey = 'bundle-selection' + . ($this->useRegularPrice ? 'regular-' : '') + . '-amount-' + . $product->getSelectionId(); if ($product->hasData($bundleSelectionKey)) { return $product->getData($bundleSelectionKey); } @@ -168,6 +175,7 @@ public function getAmount() ); $product->setData($bundleSelectionKey, $this->amount[$value]); } + return $this->amount[$value]; } diff --git a/app/code/Magento/Bundle/Pricing/Price/ConfiguredPrice.php b/app/code/Magento/Bundle/Pricing/Price/ConfiguredPrice.php index 8eb2fbb216b1..11f7e2f3d1f1 100644 --- a/app/code/Magento/Bundle/Pricing/Price/ConfiguredPrice.php +++ b/app/code/Magento/Bundle/Pricing/Price/ConfiguredPrice.php @@ -11,11 +11,13 @@ use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; use Magento\Catalog\Pricing\Price as CatalogPrice; use Magento\Catalog\Pricing\Price\ConfiguredPriceInterface; +use Magento\Catalog\Pricing\Price\ConfiguredPriceSelection; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; /** * Configured price model * @api - * @since 100.0.2 */ class ConfiguredPrice extends CatalogPrice\FinalPrice implements ConfiguredPriceInterface { @@ -37,29 +39,39 @@ class ConfiguredPrice extends CatalogPrice\FinalPrice implements ConfiguredPrice /** * Serializer interface instance. * - * @var \Magento\Framework\Serialize\Serializer\Json + * @var JsonSerializer */ private $serializer; + /** + * @var ConfiguredPriceSelection + */ + private $configuredPriceSelection; + /** * @param Product $saleableItem * @param float $quantity * @param BundleCalculatorInterface $calculator - * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param PriceCurrencyInterface $priceCurrency * @param ItemInterface $item - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer + * @param JsonSerializer|null $serializer + * @param ConfiguredPriceSelection|null $configuredPriceSelection */ public function __construct( Product $saleableItem, $quantity, BundleCalculatorInterface $calculator, - \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, + PriceCurrencyInterface $priceCurrency, ItemInterface $item = null, - \Magento\Framework\Serialize\Serializer\Json $serializer = null + JsonSerializer $serializer = null, + ConfiguredPriceSelection $configuredPriceSelection = null ) { $this->item = $item; $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\Serializer\Json::class); + ->get(JsonSerializer::class); + $this->configuredPriceSelection = $configuredPriceSelection + ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(ConfiguredPriceSelection::class); parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); } @@ -74,7 +86,7 @@ public function setItem(ItemInterface $item) } /** - * Get Options with attached Selections collection + * Get Options with attached Selections collection. * * @return array|\Magento\Bundle\Model\ResourceModel\Option\Collection */ @@ -84,13 +96,14 @@ public function getOptions() $bundleOptions = []; /** @var \Magento\Bundle\Model\Product\Type $typeInstance */ $typeInstance = $bundleProduct->getTypeInstance(); - - // get bundle options - $optionsQuoteItemOption = $this->item->getOptionByCode('bundle_option_ids'); - $bundleOptionsIds = $optionsQuoteItemOption - ? $this->serializer->unserialize($optionsQuoteItemOption->getValue()) - : []; - + $bundleOptionsIds = []; + if ($this->item !== null) { + // get bundle options + $optionsQuoteItemOption = $this->item->getOptionByCode('bundle_option_ids'); + if ($optionsQuoteItemOption && $optionsQuoteItemOption->getValue()) { + $bundleOptionsIds = $this->serializer->unserialize($optionsQuoteItemOption->getValue()); + } + } if ($bundleOptionsIds) { /** @var \Magento\Bundle\Model\ResourceModel\Option\Collection $optionsCollection */ $optionsCollection = $typeInstance->getOptionsByIds($bundleOptionsIds, $bundleProduct); @@ -102,24 +115,20 @@ public function getOptions() $bundleOptions = $optionsCollection->appendSelections($selectionsCollection, true); } } + return $bundleOptions; } /** - * Option amount calculation for bundle product + * Option amount calculation for bundle product. * * @param float $baseValue * @return \Magento\Framework\Pricing\Amount\AmountInterface */ public function getConfiguredAmount($baseValue = 0.) { - $selectionPriceList = []; - foreach ($this->getOptions() as $option) { - $selectionPriceList = array_merge( - $selectionPriceList, - $this->calculator->createSelectionPriceList($option, $this->product) - ); - } + $selectionPriceList = $this->configuredPriceSelection->getSelectionPriceList($this); + return $this->calculator->calculateBundleAmount( $baseValue, $this->product, diff --git a/app/code/Magento/Bundle/Pricing/Price/ConfiguredRegularPrice.php b/app/code/Magento/Bundle/Pricing/Price/ConfiguredRegularPrice.php new file mode 100644 index 000000000000..7eaee6d0fae6 --- /dev/null +++ b/app/code/Magento/Bundle/Pricing/Price/ConfiguredRegularPrice.php @@ -0,0 +1,30 @@ +calculator->createSelectionPriceList($option, $this->product, true); + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php index 97e8098b8181..ec250756d5b2 100644 --- a/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php @@ -198,7 +198,7 @@ public function testGetJsonConfigFixedPriceBundle() [ ['price' => new \Magento\Framework\DataObject( ['base_amount' => $baseAmount, 'value' => $basePriceValue] - )] + )], ], true, true @@ -244,12 +244,14 @@ public function testGetJsonConfigFixedPriceBundle() ), ] ); + $bundleOptionPriceMock = $this->getAmountPriceMock( + $baseAmount, + $regularPriceMock, + [['item' => $selections[0], 'value' => $basePriceValue, 'base_amount' => 321321]] + ); $prices = [ - 'bundle_option' => $this->getAmountPriceMock( - $baseAmount, - $regularPriceMock, - [['item' => $selections[0], 'value' => $basePriceValue, 'base_amount' => 321321]] - ), + 'bundle_option' => $bundleOptionPriceMock, + 'bundle_option_regular_price' => $bundleOptionPriceMock, \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE => $finalPriceMock, \Magento\Catalog\Pricing\Price\RegularPrice::PRICE_CODE => $regularPriceMock, ]; @@ -261,8 +263,8 @@ public function testGetJsonConfigFixedPriceBundle() $preconfiguredValues = new \Magento\Framework\DataObject( [ 'bundle_option' => [ - 1 => 123123111 - ] + 1 => 123123111, + ], ] ); $this->product->expects($this->once()) diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionPriceTest.php index b6485d0e441e..91755cb24178 100644 --- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionPriceTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionPriceTest.php @@ -7,96 +7,48 @@ namespace Magento\Bundle\Test\Unit\Pricing\Price; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\Pricing\Amount\AmountInterface; +use Magento\Catalog\Model\Product; +use Magento\Bundle\Pricing\Price\BundleOptions; +use Magento\Bundle\Pricing\Adjustment\Calculator; +use \Magento\Bundle\Model\Selection; -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ class BundleOptionPriceTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Bundle\Pricing\Price\BundleOptionPrice */ - protected $bundleOptionPrice; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $baseCalculator; + private $bundleOptionPrice; /** * @var ObjectManagerHelper */ - protected $objectManagerHelper; + private $objectManagerHelper; /** * @var \Magento\Framework\Pricing\SaleableInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $saleableItemMock; + private $saleableItemMock; /** * @var \Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $bundleCalculatorMock; + private $bundleCalculatorMock; /** - * @var \Magento\Bundle\Pricing\Price\BundleSelectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @var BundleOptions|\PHPUnit_Framework_MockObject_MockObject */ - protected $selectionFactoryMock; + private $bundleOptionsMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @inheritdoc */ - protected $amountFactory; - - /** - * @var \Magento\Framework\Pricing\PriceInfo\Base|\PHPUnit_Framework_MockObject_MockObject - */ - protected $priceInfoMock; - protected function setUp() { - $this->priceInfoMock = $this->createMock(\Magento\Framework\Pricing\PriceInfo\Base::class); - $this->saleableItemMock = $this->createMock(\Magento\Catalog\Model\Product::class); - $priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class)->getMock(); - $this->saleableItemMock->expects($this->once()) - ->method('getPriceInfo') - ->will($this->returnValue($this->priceInfoMock)); + $this->bundleOptionsMock = $this->createMock(BundleOptions::class); + $this->saleableItemMock = $this->createMock(Product::class); + $this->bundleCalculatorMock = $this->createMock(Calculator::class); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - $priceCurrency->expects($this->any())->method('round')->will($this->returnArgument(0)); - - $this->saleableItemMock->expects($this->once()) - ->method('setQty') - ->will($this->returnSelf()); - - $this->saleableItemMock->expects($this->any()) - ->method('getStore') - ->will($this->returnValue($store)); - - $this->selectionFactoryMock = $this->getMockBuilder(\Magento\Bundle\Pricing\Price\BundleSelectionFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->amountFactory = $this->createMock(\Magento\Framework\Pricing\Amount\AmountFactory::class); - $factoryCallback = $this->returnCallback( - function ($fullAmount, $adjustments) { - return $this->createAmountMock(['amount' => $fullAmount, 'adjustmentAmounts' => $adjustments]); - } - ); - $this->amountFactory->expects($this->any())->method('create')->will($factoryCallback); - $this->baseCalculator = $this->createMock(\Magento\Framework\Pricing\Adjustment\Calculator::class); - - $taxData = $this->getMockBuilder(\Magento\Tax\Helper\Data::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->bundleCalculatorMock = $this->getMockBuilder(\Magento\Bundle\Pricing\Adjustment\Calculator::class) - ->setConstructorArgs( - [$this->baseCalculator, $this->amountFactory, $this->selectionFactoryMock, $taxData, $priceCurrency] - ) - ->setMethods(['getOptionsAmount']) - ->getMock(); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->bundleOptionPrice = $this->objectManagerHelper->getObject( \Magento\Bundle\Pricing\Price\BundleOptionPrice::class, @@ -104,107 +56,50 @@ function ($fullAmount, $adjustments) { 'saleableItem' => $this->saleableItemMock, 'quantity' => 1., 'calculator' => $this->bundleCalculatorMock, - 'bundleSelectionFactory' => $this->selectionFactoryMock + 'bundleOptions' => $this->bundleOptionsMock, ] ); } /** - * @dataProvider getOptionsDataProvider - */ - public function testGetOptions($selectionCollection) - { - $this->prepareOptionMocks($selectionCollection); - $this->assertSame($selectionCollection, $this->bundleOptionPrice->getOptions()); - $this->assertSame($selectionCollection, $this->bundleOptionPrice->getOptions()); - } - - /** - * @param array $selectionCollection + * Test method \Magento\Bundle\Pricing\Price\BundleOptionPrice::getOptions + * * @return void */ - protected function prepareOptionMocks($selectionCollection) + public function testGetOptions() { - $this->saleableItemMock->expects($this->atLeastOnce()) - ->method('getStoreId') - ->will($this->returnValue(1)); - - $priceTypeMock = $this->createMock(\Magento\Bundle\Model\Product\Type::class); - $priceTypeMock->expects($this->atLeastOnce()) - ->method('setStoreFilter') - ->with($this->equalTo(1), $this->equalTo($this->saleableItemMock)) - ->will($this->returnSelf()); - - $optionIds = ['41', '55']; - $priceTypeMock->expects($this->atLeastOnce()) - ->method('getOptionsIds') - ->with($this->equalTo($this->saleableItemMock)) - ->will($this->returnValue($optionIds)); - - $priceTypeMock->expects($this->atLeastOnce()) - ->method('getSelectionsCollection') - ->with($this->equalTo($optionIds), $this->equalTo($this->saleableItemMock)) - ->will($this->returnValue($selectionCollection)); - $collection = $this->createMock(\Magento\Bundle\Model\ResourceModel\Option\Collection::class); - $collection->expects($this->atLeastOnce()) - ->method('appendSelections') - ->with($this->equalTo($selectionCollection), $this->equalTo(true), $this->equalTo(false)) - ->will($this->returnValue($selectionCollection)); - - $priceTypeMock->expects($this->atLeastOnce()) - ->method('getOptionsCollection') - ->with($this->equalTo($this->saleableItemMock)) + $this->bundleOptionsMock->expects($this->any()) + ->method('getOptions') ->will($this->returnValue($collection)); - - $this->saleableItemMock->expects($this->atLeastOnce()) - ->method('getTypeInstance') - ->will($this->returnValue($priceTypeMock)); - } - - public function getOptionsDataProvider() - { - return [ - ['1', '2'] - ]; + $this->assertEquals($collection, $this->bundleOptionPrice->getOptions()); } /** - * @param float $selectionQty - * @param float|bool $selectionAmount - * @dataProvider selectionAmountDataProvider + * Test method \Magento\Bundle\Pricing\Price\BundleOptionPrice::getOptionSelectionAmount + * + * @return void */ - public function testGetOptionSelectionAmount($selectionQty, $selectionAmount) + public function testGetOptionSelectionAmount() { - $selection = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['getSelectionQty', '__wakeup']); - $selection->expects($this->once()) - ->method('getSelectionQty') - ->will($this->returnValue($selectionQty)); - $priceMock = $this->createMock(\Magento\Bundle\Pricing\Price\BundleSelectionPrice::class); - $priceMock->expects($this->once()) - ->method('getAmount') - ->will($this->returnValue($selectionAmount)); - $this->selectionFactoryMock->expects($this->once()) - ->method('create') - ->with($this->equalTo($this->saleableItemMock), $this->equalTo($selection), $this->equalTo($selectionQty)) - ->will($this->returnValue($priceMock)); - $this->assertSame($selectionAmount, $this->bundleOptionPrice->getOptionSelectionAmount($selection)); + $selectionAmount = $this->createMock(AmountInterface::class); + $product = $this->createMock(Product::class); + $selection = $this->createMock(Selection::class); + $this->bundleOptionsMock->expects($this->any()) + ->method('getOptionSelectionAmount') + ->will($this->returnValue($selectionAmount)) + ->with($product, $selection, false); + $this->assertEquals($selectionAmount, $this->bundleOptionPrice->getOptionSelectionAmount($selection)); } /** - * @return array + * Test method \Magento\Bundle\Pricing\Price\BundleOptionPrice::getAmount + * + * @return void */ - public function selectionAmountDataProvider() - { - return [ - [1., 50.5], - [2.2, false] - ]; - } - public function testGetAmount() { - $amountMock = $this->createMock(\Magento\Framework\Pricing\Amount\AmountInterface::class); + $amountMock = $this->createMock(AmountInterface::class); $this->bundleCalculatorMock->expects($this->once()) ->method('getOptionsAmount') ->with($this->equalTo($this->saleableItemMock)) @@ -213,204 +108,14 @@ public function testGetAmount() } /** - * Create amount mock - * - * @param array $amountData - * @return \Magento\Framework\Pricing\Amount\Base|\PHPUnit_Framework_MockObject_MockObject - */ - protected function createAmountMock($amountData) - { - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Pricing\Amount\Base $amount */ - $amount = $this->createMock(\Magento\Framework\Pricing\Amount\Base::class); - $amount->expects($this->any())->method('getAdjustmentAmounts')->will( - $this->returnValue(isset($amountData['adjustmentAmounts']) ? $amountData['adjustmentAmounts'] : []) - ); - $amount->expects($this->any())->method('getValue')->will($this->returnValue($amountData['amount'])); - return $amount; - } - - /** - * Create option mock + * Test method \Magento\Bundle\Pricing\Price\BundleOptionPrice::getValue * - * @param array $optionData - * @return \Magento\Bundle\Model\Option|\PHPUnit_Framework_MockObject_MockObject - */ - protected function createOptionMock($optionData) - { - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Bundle\Model\Option $option */ - $option = $this->createPartialMock(\Magento\Bundle\Model\Option::class, ['isMultiSelection', '__wakeup']); - $option->expects($this->any())->method('isMultiSelection') - ->will($this->returnValue($optionData['isMultiSelection'])); - $selections = []; - foreach ($optionData['selections'] as $selectionData) { - $selections[] = $this->createSelectionMock($selectionData); - } - foreach ($optionData['data'] as $key => $value) { - $option->setData($key, $value); - } - $option->setData('selections', $selections); - return $option; - } - - /** - * Create selection product mock - * - * @param array $selectionData - * @return \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject - */ - protected function createSelectionMock($selectionData) - { - $selection = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->setMethods(['isSalable', 'getAmount', 'getQuantity', 'getProduct', '__wakeup']) - ->disableOriginalConstructor() - ->getMock(); - - // All items are saleable - $selection->expects($this->any())->method('isSalable')->will($this->returnValue(true)); - foreach ($selectionData['data'] as $key => $value) { - $selection->setData($key, $value); - } - $amountMock = $this->createAmountMock($selectionData['amount']); - $selection->expects($this->any())->method('getAmount')->will($this->returnValue($amountMock)); - $selection->expects($this->any())->method('getQuantity')->will($this->returnValue(1)); - - $innerProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->setMethods(['getSelectionCanChangeQty', '__wakeup']) - ->disableOriginalConstructor() - ->getMock(); - $innerProduct->expects($this->any())->method('getSelectionCanChangeQty')->will($this->returnValue(true)); - $selection->expects($this->any())->method('getProduct')->will($this->returnValue($innerProduct)); - - return $selection; - } - - /** - * @dataProvider getTestDataForCalculation - */ - public function testCalculation($optionList, $expected) - { - $storeId = 1; - $this->saleableItemMock->expects($this->any())->method('getStoreId')->will($this->returnValue($storeId)); - $this->selectionFactoryMock->expects($this->any())->method('create')->will($this->returnArgument(1)); - - $this->baseCalculator->expects($this->atLeastOnce())->method('getAmount') - ->will($this->returnValue($this->createAmountMock(['amount' => 0.]))); - - $options = []; - foreach ($optionList as $optionData) { - $options[] = $this->createOptionMock($optionData); - } - /** @var \PHPUnit_Framework_MockObject_MockObject $optionsCollection */ - $optionsCollection = $this->createMock(\Magento\Bundle\Model\ResourceModel\Option\Collection::class); - $optionsCollection->expects($this->atLeastOnce())->method('appendSelections')->will($this->returnSelf()); - $optionsCollection->expects($this->atLeastOnce())->method('getIterator') - ->will($this->returnValue(new \ArrayIterator($options))); - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product\Type\AbstractType $typeMock */ - $typeMock = $this->createMock(\Magento\Bundle\Model\Product\Type::class); - $typeMock->expects($this->any())->method('setStoreFilter')->with($storeId, $this->saleableItemMock); - $typeMock->expects($this->any())->method('getOptionsCollection')->with($this->saleableItemMock) - ->will($this->returnValue($optionsCollection)); - $this->saleableItemMock->expects($this->any())->method('getTypeInstance')->will($this->returnValue($typeMock)); - - $this->assertEquals($expected['min'], $this->bundleOptionPrice->getValue()); - $this->assertEquals($expected['max'], $this->bundleOptionPrice->getMaxValue()); - } - - /** - * @return array + * @return void */ - public function getTestDataForCalculation() + public function testGetValue() { - return [ - 'first case' => [ - 'optionList' => [ - // first option with single choice of product - [ - 'isMultiSelection' => false, - 'data' => [ - 'title' => 'test option 1', - 'default_title' => 'test option 1', - 'type' => 'select', - 'option_id' => '1', - 'position' => '0', - 'required' => '1', - ], - 'selections' => [ - [ - 'data' => ['price' => 70.], - 'amount' => ['amount' => 70], - ], - [ - 'data' => ['price' => 80.], - 'amount' => ['amount' => 80] - ], - [ - 'data' => ['price' => 50.], - 'amount' => ['amount' => 50] - ], - ] - ], - // second not required option - [ - 'isMultiSelection' => false, - 'data' => [ - 'title' => 'test option 2', - 'default_title' => 'test option 2', - 'type' => 'select', - 'option_id' => '2', - 'position' => '1', - 'required' => '0', - ], - 'selections' => [ - [ - 'data' => ['value' => 20.], - 'amount' => ['amount' => 20], - ], - ] - ], - // third with multi-selection - [ - 'isMultiSelection' => true, - 'data' => [ - 'title' => 'test option 3', - 'default_title' => 'test option 3', - 'type' => 'select', - 'option_id' => '3', - 'position' => '2', - 'required' => '1', - ], - 'selections' => [ - [ - 'data' => ['price' => 40.], - 'amount' => ['amount' => 40], - ], - [ - 'data' => ['price' => 20.], - 'amount' => ['amount' => 20] - ], - [ - 'data' => ['price' => 60.], - 'amount' => ['amount' => 60] - ], - ] - ], - // fourth without selections - [ - 'isMultiSelection' => true, - 'data' => [ - 'title' => 'test option 3', - 'default_title' => 'test option 3', - 'type' => 'select', - 'option_id' => '4', - 'position' => '3', - 'required' => '1', - ], - 'selections' => [] - ], - ], - 'expected' => ['min' => 70, 'max' => 220], - ] - ]; + $value = 1; + $this->bundleOptionsMock->expects($this->any())->method('calculateOptions')->will($this->returnValue($value)); + $this->assertEquals($value, $this->bundleOptionPrice->getValue()); } } diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionRegularPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionRegularPriceTest.php new file mode 100644 index 000000000000..33ccc2cfb8af --- /dev/null +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionRegularPriceTest.php @@ -0,0 +1,127 @@ +bundleOptionsMock = $this->createMock(BundleOptions::class); + $this->saleableItemMock = $this->createMock(Product::class); + $this->bundleCalculatorMock = $this->createMock(Calculator::class); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->bundleOptionRegularPrice = $this->objectManagerHelper->getObject( + BundleOptionRegularPrice::class, + [ + 'saleableItem' => $this->saleableItemMock, + 'quantity' => 1., + 'calculator' => $this->bundleCalculatorMock, + 'bundleOptions' => $this->bundleOptionsMock, + ] + ); + } + + /** + * Test method \Magento\Bundle\Pricing\Price\BundleOptionRegularPrice::getOptions + * + * @return void + */ + public function testGetOptions() + { + $collection = $this->createMock(Collection::class); + $this->bundleOptionsMock->expects($this->any()) + ->method('getOptions') + ->will($this->returnValue($collection)); + $this->assertEquals($collection, $this->bundleOptionRegularPrice->getOptions()); + } + + /** + * Test method \Magento\Bundle\Pricing\Price\BundleOptionRegularPrice::getOptionSelectionAmount + * + * @return void + */ + public function testGetOptionSelectionAmount() + { + $selectionAmount = $this->createMock(AmountInterface::class); + $product = $this->createMock(Product::class); + $selection = $this->createMock(Selection::class); + $this->bundleOptionsMock->expects($this->any()) + ->method('getOptionSelectionAmount') + ->will($this->returnValue($selectionAmount)) + ->with($product, $selection, true); + $this->assertEquals($selectionAmount, $this->bundleOptionRegularPrice->getOptionSelectionAmount($selection)); + } + + /** + * Test method \Magento\Bundle\Pricing\Price\BundleOptionRegularPrice::getAmount + * + * @return void + */ + public function testGetAmount() + { + $amountMock = $this->createMock(AmountInterface::class); + $this->bundleCalculatorMock->expects($this->once()) + ->method('getOptionsAmount') + ->with($this->equalTo($this->saleableItemMock)) + ->will($this->returnValue($amountMock)); + $this->assertSame($amountMock, $this->bundleOptionRegularPrice->getAmount()); + } + + /** + * Test method \Magento\Bundle\Pricing\Price\BundleOptionRegularPrice::getValue + * + * @return void + */ + public function testGetValue() + { + $value = 1; + $this->bundleOptionsMock->expects($this->any())->method('calculateOptions')->will($this->returnValue($value)); + $this->assertEquals($value, $this->bundleOptionRegularPrice->getValue()); + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionsTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionsTest.php new file mode 100644 index 000000000000..37973b9b8ae2 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleOptionsTest.php @@ -0,0 +1,449 @@ +priceInfoMock = $this->getMockBuilder(BasePriceInfo::class) + ->disableOriginalConstructor() + ->getMock(); + $this->saleableItemMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + $priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)->getMock(); + $priceCurrency->expects($this->any())->method('round')->willReturnArgument(0); + + $this->selectionFactoryMock = $this->getMockBuilder(BundleSelectionFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->amountFactory = $this->getMockBuilder(AmountFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $factoryCallback = $this->returnCallback( + function ($fullAmount, $adjustments) { + return $this->createAmountMock(['amount' => $fullAmount, 'adjustmentAmounts' => $adjustments]); + } + ); + $this->amountFactory->expects($this->any())->method('create')->will($factoryCallback); + $this->baseCalculator = $this->getMockBuilder(AdjustmentCalculator::class) + ->disableOriginalConstructor() + ->getMock(); + + $taxData = $this->getMockBuilder(TaxHelperData::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->bundleCalculatorMock = $this->getMockBuilder(BundleAdjustmentCalculator::class) + ->setConstructorArgs( + [$this->baseCalculator, $this->amountFactory, $this->selectionFactoryMock, $taxData, $priceCurrency] + ) + ->setMethods(['getOptionsAmount']) + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->bundleOptions = $this->objectManagerHelper->getObject( + BundleOptions::class, + [ + 'calculator' => $this->bundleCalculatorMock, + 'bundleSelectionFactory' => $this->selectionFactoryMock, + ] + ); + } + + /** + * @dataProvider getOptionsDataProvider + * @param array $selectionCollection + * + * @return void + */ + public function testGetOptions(array $selectionCollection) + { + $this->prepareOptionMocks($selectionCollection); + $this->bundleOptions->getOptions($this->saleableItemMock); + $this->assertSame($selectionCollection, $this->bundleOptions->getOptions($this->saleableItemMock)); + } + + /** + * @param array $selectionCollection + * + * @return void + */ + private function prepareOptionMocks(array $selectionCollection) + { + $this->saleableItemMock->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn(1); + $priceTypeMock = $this->getMockBuilder(BundleProductType::class) + ->disableOriginalConstructor() + ->getMock(); + $priceTypeMock->expects($this->atLeastOnce()) + ->method('setStoreFilter') + ->with(1, $this->saleableItemMock) + ->willReturnSelf(); + $optionIds = ['41', '55']; + $priceTypeMock->expects($this->atLeastOnce()) + ->method('getOptionsIds') + ->with($this->saleableItemMock) + ->willReturn($optionIds); + $priceTypeMock->expects($this->atLeastOnce()) + ->method('getSelectionsCollection') + ->with($optionIds, $this->saleableItemMock) + ->willReturn($selectionCollection); + $collection = $this->getMockBuilder(BundleOptionCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $collection->expects($this->atLeastOnce()) + ->method('appendSelections') + ->with($selectionCollection, true, false) + ->willReturn($selectionCollection); + $priceTypeMock->expects($this->atLeastOnce()) + ->method('getOptionsCollection') + ->with($this->saleableItemMock) + ->willReturn($collection); + $this->saleableItemMock->expects($this->atLeastOnce()) + ->method('getTypeInstance') + ->willReturn($priceTypeMock); + } + + /** + * @return array + */ + public function getOptionsDataProvider() : array + { + return [ + [ + ['1', '2'], + ], + ]; + } + + /** + * @dataProvider selectionAmountDataProvider + * + * @param float $selectionQty + * @param float|bool $selectionAmount + * @param bool $useRegularPrice + * + * @return void + */ + public function testGetOptionSelectionAmount(float $selectionQty, $selectionAmount, bool $useRegularPrice) + { + $selection = $this->createPartialMock(Product::class, ['getSelectionQty', '__wakeup']); + $amountInterfaceMock = $this->getMockBuilder(AmountInterface::class) + ->getMockForAbstractClass(); + $amountInterfaceMock->expects($this->once()) + ->method('getValue') + ->willReturn($selectionAmount); + $selection->expects($this->once()) + ->method('getSelectionQty') + ->willReturn($selectionQty); + $priceMock = $this->getMockBuilder(BundleSelectionPrice::class) + ->disableOriginalConstructor() + ->getMock(); + $priceMock->expects($this->once()) + ->method('getAmount') + ->willReturn($amountInterfaceMock); + $this->selectionFactoryMock->expects($this->once()) + ->method('create') + ->with($this->saleableItemMock, $selection, $selectionQty) + ->willReturn($priceMock); + $optionSelectionAmount = $this->bundleOptions->getOptionSelectionAmount( + $this->saleableItemMock, + $selection, + $useRegularPrice + ); + $this->assertSame($selectionAmount, $optionSelectionAmount->getValue()); + } + + /** + * @return array + */ + public function selectionAmountDataProvider(): array + { + return [ + [1., 50.5, false], + [2.2, false, true], + ]; + } + + /** + * Create amount mock. + * + * @param array $amountData + * @return BaseAmount|MockObject + */ + private function createAmountMock(array $amountData) + { + /** @var BaseAmount|MockObject $amount */ + $amount = $this->getMockBuilder(BaseAmount::class) + ->disableOriginalConstructor() + ->getMock(); + $amount->expects($this->any())->method('getAdjustmentAmounts') + ->willReturn($amountData['adjustmentAmounts'] ?? []); + $amount->expects($this->any())->method('getValue')->willReturn($amountData['amount']); + + return $amount; + } + + /** + * Create option mock. + * + * @param array $optionData + * @return BundleOption|MockObject + */ + private function createOptionMock(array $optionData) + { + /** @var BundleOption|MockObject $option */ + $option = $this->createPartialMock(BundleOption::class, ['isMultiSelection', '__wakeup']); + $option->expects($this->any())->method('isMultiSelection') + ->willReturn($optionData['isMultiSelection']); + $selections = []; + foreach ($optionData['selections'] as $selectionData) { + $selections[] = $this->createSelectionMock($selectionData); + } + foreach ($optionData['data'] as $key => $value) { + $option->setData($key, $value); + } + $option->setData('selections', $selections); + + return $option; + } + + /** + * Create selection product mock. + * + * @param array $selectionData + * @return Product|MockObject + */ + private function createSelectionMock(array $selectionData) + { + $selection = $this->getMockBuilder(Product::class) + ->setMethods(['isSalable', 'getAmount', 'getQuantity', 'getProduct', '__wakeup']) + ->disableOriginalConstructor() + ->getMock(); + + // All items are saleable + $selection->expects($this->any())->method('isSalable')->willReturn(true); + foreach ($selectionData['data'] as $key => $value) { + $selection->setData($key, $value); + } + $amountMock = $this->createAmountMock($selectionData['amount']); + $selection->expects($this->any())->method('getAmount')->willReturn($amountMock); + $selection->expects($this->any())->method('getQuantity')->willReturn(1); + + $innerProduct = $this->getMockBuilder(Product::class) + ->setMethods(['getSelectionCanChangeQty', '__wakeup']) + ->disableOriginalConstructor() + ->getMock(); + $innerProduct->expects($this->any())->method('getSelectionCanChangeQty')->willReturn(true); + $selection->expects($this->any())->method('getProduct')->willReturn($innerProduct); + + return $selection; + } + + /** + * @dataProvider getTestDataForCalculation + * @param array $optionList + * @param array $expected + * + * @return void + */ + public function testCalculation(array $optionList, array $expected) + { + $storeId = 1; + $this->saleableItemMock->expects($this->any())->method('getStoreId')->willReturn($storeId); + $this->selectionFactoryMock->expects($this->any())->method('create')->willReturnArgument(1); + + $this->baseCalculator->expects($this->atLeastOnce())->method('getAmount') + ->willReturn($this->createAmountMock(['amount' => 0.])); + + $options = []; + foreach ($optionList as $optionData) { + $options[] = $this->createOptionMock($optionData); + } + /** @var BundleOptionCollection|MockObject $optionsCollection */ + $optionsCollection = $this->getMockBuilder(BundleOptionCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $optionsCollection->expects($this->atLeastOnce())->method('appendSelections')->willReturn($options); + + /** @var \Magento\Catalog\Model\Product\Type\AbstractType|MockObject $typeMock */ + $typeMock = $this->getMockBuilder(BundleProductType::class) + ->disableOriginalConstructor() + ->getMock(); + $typeMock->expects($this->any())->method('setStoreFilter') + ->with($storeId, $this->saleableItemMock); + $typeMock->expects($this->any())->method('getOptionsCollection') + ->with($this->saleableItemMock) + ->willReturn($optionsCollection); + $this->saleableItemMock->expects($this->any())->method('getTypeInstance')->willReturn($typeMock); + + $this->assertEquals($expected['min'], $this->bundleOptions->calculateOptions($this->saleableItemMock)); + $this->assertEquals($expected['max'], $this->bundleOptions->calculateOptions($this->saleableItemMock, false)); + } + + /** + * @return array + */ + public function getTestDataForCalculation(): array + { + return [ + 'first case' => [ + 'optionList' => [ + // first option with single choice of product + [ + 'isMultiSelection' => false, + 'data' => [ + 'title' => 'test option 1', + 'default_title' => 'test option 1', + 'type' => 'select', + 'option_id' => '1', + 'position' => '0', + 'required' => '1', + ], + 'selections' => [ + [ + 'data' => ['price' => 70.], + 'amount' => ['amount' => 70], + ], + [ + 'data' => ['price' => 80.], + 'amount' => ['amount' => 80], + ], + [ + 'data' => ['price' => 50.], + 'amount' => ['amount' => 50], + ], + ], + ], + // second not required option + [ + 'isMultiSelection' => false, + 'data' => [ + 'title' => 'test option 2', + 'default_title' => 'test option 2', + 'type' => 'select', + 'option_id' => '2', + 'position' => '1', + 'required' => '0', + ], + 'selections' => [ + [ + 'data' => ['value' => 20.], + 'amount' => ['amount' => 20], + ], + ], + ], + // third with multi-selection + [ + 'isMultiSelection' => true, + 'data' => [ + 'title' => 'test option 3', + 'default_title' => 'test option 3', + 'type' => 'select', + 'option_id' => '3', + 'position' => '2', + 'required' => '1', + ], + 'selections' => [ + [ + 'data' => ['price' => 40.], + 'amount' => ['amount' => 40], + ], + [ + 'data' => ['price' => 20.], + 'amount' => ['amount' => 20], + ], + [ + 'data' => ['price' => 60.], + 'amount' => ['amount' => 60], + ], + ], + ], + // fourth without selections + [ + 'isMultiSelection' => true, + 'data' => [ + 'title' => 'test option 3', + 'default_title' => 'test option 3', + 'type' => 'select', + 'option_id' => '4', + 'position' => '3', + 'required' => '1', + ], + 'selections' => [], + ], + ], + 'expected' => ['min' => 70, 'max' => 220], + ], + ]; + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php index 700bfd4f3c08..d43b0575aea9 100644 --- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php @@ -163,12 +163,14 @@ public function testGetValueTypeDynamic($useRegularPrice) } /** - * test for method getValue with type Fixed and selectionPriceType not null + * Test for method getValue with type Fixed and selectionPriceType not null. * * @param bool $useRegularPrice * @dataProvider useRegularPriceDataProvider + * + * @return void */ - public function testGetValueTypeFixedWithSelectionPriceType($useRegularPrice) + public function testGetValueTypeFixedWithSelectionPriceType(bool $useRegularPrice) { $this->setupSelectionPrice($useRegularPrice); $regularPrice = 100.125; @@ -178,54 +180,49 @@ public function testGetValueTypeFixedWithSelectionPriceType($useRegularPrice) $this->bundleMock->expects($this->once()) ->method('getPriceType') - ->will($this->returnValue(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED)); + ->willReturn(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED); $this->bundleMock->expects($this->atLeastOnce()) ->method('getPriceInfo') - ->will($this->returnValue($this->priceInfoMock)); + ->willReturn($this->priceInfoMock); $this->priceInfoMock->expects($this->once()) ->method('getPrice') - ->with($this->equalTo(RegularPrice::PRICE_CODE)) - ->will($this->returnValue($this->regularPriceMock)); + ->with(RegularPrice::PRICE_CODE) + ->willReturn($this->regularPriceMock); $this->regularPriceMock->expects($this->once()) ->method('getValue') - ->will($this->returnValue($actualPrice)); + ->willReturn($actualPrice); $this->bundleMock->expects($this->once()) ->method('setFinalPrice') - ->will($this->returnSelf()); + ->willReturnSelf(); $this->eventManagerMock->expects($this->once()) ->method('dispatch'); $this->bundleMock->expects($this->exactly(2)) ->method('getData') - ->will( - $this->returnValueMap( - [ - ['qty', null, 1], - ['final_price', null, 100], - ['price', null, 100], - ] - ) + ->willReturnMap( + [ + ['qty', null, 1], + ['final_price', null, 100], + ['price', null, 100], + ] ); $this->productMock->expects($this->once()) ->method('getSelectionPriceType') - ->will($this->returnValue(true)); + ->willReturn(true); $this->productMock->expects($this->any()) ->method('getSelectionPriceValue') - ->will($this->returnValue($actualPrice)); + ->willReturn($actualPrice); if (!$useRegularPrice) { $this->discountCalculatorMock->expects($this->once()) ->method('calculateDiscount') - ->with( - $this->equalTo($this->bundleMock), - $this->equalTo($actualPrice) - ) - ->will($this->returnValue($discountedPrice)); + ->with($this->bundleMock, $actualPrice) + ->willReturn($discountedPrice); } $this->priceCurrencyMock->expects($this->once()) ->method('round') ->with($actualPrice) - ->will($this->returnValue($expectedPrice)); + ->willReturn($expectedPrice); $this->assertEquals($expectedPrice, $this->selectionPrice->getValue()); } diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index 066fffc035ee..aa2b9e12f770 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -25,7 +25,7 @@ }, "suggest": { "magento/module-webapi": "*", - "magento/module-bundle-sample-data": "Sample Data version:100.3.*" + "magento/module-bundle-sample-data": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index 287a6c8bfdbc..b7fba3937ded 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -50,7 +50,9 @@ Magento\Catalog\Pricing\Price\CustomOptionPrice Magento\Catalog\Pricing\Price\BasePrice Magento\Bundle\Pricing\Price\ConfiguredPrice + Magento\Bundle\Pricing\Price\ConfiguredRegularPrice Magento\Bundle\Pricing\Price\BundleOptionPrice + Magento\Bundle\Pricing\Price\BundleOptionRegularPrice Magento\CatalogRule\Pricing\Price\CatalogRulePrice @@ -75,6 +77,11 @@ Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface + + + Magento\Bundle\Pricing\Adjustment\BundleCalculatorInterface + + diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Front.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Front.php index cc50dbde69ee..a0ca53dce4f5 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Front.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Front.php @@ -184,33 +184,19 @@ protected function _prepareForm() 'form_after', $this->getLayout()->createBlock( \Magento\Backend\Block\Widget\Form\Element\Dependence::class - )->addFieldMap( - "is_wysiwyg_enabled", - 'wysiwyg_enabled' )->addFieldMap( "is_html_allowed_on_front", 'html_allowed_on_front' )->addFieldMap( "frontend_input", 'frontend_input_type' - )->addFieldDependence( - 'wysiwyg_enabled', - 'frontend_input_type', - 'textarea' - )->addFieldDependence( - 'html_allowed_on_front', - 'wysiwyg_enabled', - '0' - ) - ->addFieldMap( + )->addFieldMap( "is_searchable", 'searchable' - ) - ->addFieldMap( + )->addFieldMap( "is_visible_in_advanced_search", 'advanced_search' - ) - ->addFieldDependence( + )->addFieldDependence( 'advanced_search', 'searchable', '1' diff --git a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php index d4af775ad20d..f22edd91e791 100644 --- a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php +++ b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php @@ -510,9 +510,6 @@ protected function getDetailsRendererList() */ public function getImage($product, $imageId, $attributes = []) { - return $this->imageBuilder->setProduct($product) - ->setImageId($imageId) - ->setAttributes($attributes) - ->create(); + return $this->imageBuilder->create($product, $imageId, $attributes); } } diff --git a/app/code/Magento/Catalog/Block/Product/Image.php b/app/code/Magento/Catalog/Block/Product/Image.php index 3ce97bd53f8d..20a556ab4145 100644 --- a/app/code/Magento/Catalog/Block/Product/Image.php +++ b/app/code/Magento/Catalog/Block/Product/Image.php @@ -11,8 +11,6 @@ * @method string getWidth() * @method string getHeight() * @method string getLabel() - * @method mixed getResizedImageWidth() - * @method mixed getResizedImageHeight() * @method float getRatio() * @method string getCustomAttributes() * @since 100.0.2 diff --git a/app/code/Magento/Catalog/Block/Product/ImageBuilder.php b/app/code/Magento/Catalog/Block/Product/ImageBuilder.php index f1149f15c41d..06d4fb39109d 100644 --- a/app/code/Magento/Catalog/Block/Product/ImageBuilder.php +++ b/app/code/Magento/Catalog/Block/Product/ImageBuilder.php @@ -3,12 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Block\Product; use Magento\Catalog\Helper\ImageFactory as HelperFactory; use Magento\Catalog\Model\Product; -use Magento\Catalog\Model\Product\Image\NotLoadInfoImageException; +/** + * @deprecated + * @see ImageFactory + */ class ImageBuilder { /** @@ -117,39 +122,16 @@ protected function getRatio(\Magento\Catalog\Helper\Image $helper) /** * Create image block * - * @return \Magento\Catalog\Block\Product\Image + * @param Product|null $product + * @param string|null $imageId + * @param array|null $attributes + * @return Image */ - public function create() + public function create(Product $product = null, string $imageId = null, array $attributes = null) { - /** @var \Magento\Catalog\Helper\Image $helper */ - $helper = $this->helperFactory->create() - ->init($this->product, $this->imageId); - - $template = $helper->getFrame() - ? 'Magento_Catalog::product/image.phtml' - : 'Magento_Catalog::product/image_with_borders.phtml'; - - try { - $imagesize = $helper->getResizedImageInfo(); - } catch (NotLoadInfoImageException $exception) { - $imagesize = [$helper->getWidth(), $helper->getHeight()]; - } - - $data = [ - 'data' => [ - 'template' => $template, - 'image_url' => $helper->getUrl(), - 'width' => $helper->getWidth(), - 'height' => $helper->getHeight(), - 'label' => $helper->getLabel(), - 'ratio' => $this->getRatio($helper), - 'custom_attributes' => $this->getCustomAttributes(), - 'resized_image_width' => $imagesize[0], - 'resized_image_height' => $imagesize[1], - 'product_id' => $this->product->getId() - ], - ]; - - return $this->imageFactory->create($data); + $product = $product ?? $this->product; + $imageId = $imageId ?? $this->imageId; + $attributes = $attributes ?? $this->attributes; + return $this->imageFactory->create($product, $imageId, $attributes); } } diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php new file mode 100644 index 000000000000..f9a576367dde --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php @@ -0,0 +1,163 @@ +objectManager = $objectManager; + $this->presentationConfig = $presentationConfig; + $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory; + $this->viewAssetImageFactory = $viewAssetImageFactory; + $this->imageParamsBuilder = $imageParamsBuilder; + } + + /** + * Retrieve image custom attributes for HTML element + * + * @param array $attributes + * @return string + */ + private function getStringCustomAttributes(array $attributes): string + { + $result = []; + foreach ($attributes as $name => $value) { + $result[] = $name . '="' . $value . '"'; + } + return !empty($result) ? implode(' ', $result) : ''; + } + + /** + * Calculate image ratio + * + * @param $width + * @param $height + * @return float + */ + private function getRatio(int $width, int $height): float + { + if ($width && $height) { + return $height / $width; + } + return 1.0; + } + + /** + * @param Product $product + * + * @param string $imageType + * @return string + */ + private function getLabel(Product $product, string $imageType): string + { + $label = $product->getData($imageType . '_' . 'label'); + if (empty($label)) { + $label = $product->getName(); + } + return (string) $label; + } + + /** + * Create image block from product + * @param Product $product + * @param string $imageId + * @param array|null $attributes + * @return ImageBlock + */ + public function create(Product $product, string $imageId, array $attributes = null): ImageBlock + { + $viewImageConfig = $this->presentationConfig->getViewConfig()->getMediaAttributes( + 'Magento_Catalog', + ImageHelper::MEDIA_TYPE_CONFIG_NODE, + $imageId + ); + + $imageMiscParams = $this->imageParamsBuilder->build($viewImageConfig); + $originalFilePath = $product->getData($imageMiscParams['image_type']); + + if ($originalFilePath === null || $originalFilePath === 'no_selection') { + $imageAsset = $this->viewAssetPlaceholderFactory->create( + [ + 'type' => $imageMiscParams['image_type'] + ] + ); + } else { + $imageAsset = $this->viewAssetImageFactory->create( + [ + 'miscParams' => $imageMiscParams, + 'filePath' => $originalFilePath, + ] + ); + } + + $data = [ + 'data' => [ + 'template' => 'Magento_Catalog::product/image_with_borders.phtml', + 'image_url' => $imageAsset->getUrl(), + 'width' => $imageMiscParams['image_width'], + 'height' => $imageMiscParams['image_height'], + 'label' => $this->getLabel($product, $imageMiscParams['image_type']), + 'ratio' => $this->getRatio($imageMiscParams['image_width'], $imageMiscParams['image_height']), + 'custom_attributes' => $this->getStringCustomAttributes($attributes), + 'product_id' => $product->getId() + ], + ]; + + return $this->objectManager->create(ImageBlock::class, $data); + } +} diff --git a/app/code/Magento/Catalog/Block/Product/View/Gallery.php b/app/code/Magento/Catalog/Block/Product/View/Gallery.php index 90e89acfba77..ab01fc6d134e 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Gallery.php +++ b/app/code/Magento/Catalog/Block/Product/View/Gallery.php @@ -15,6 +15,7 @@ use Magento\Catalog\Helper\Image; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface; +use Magento\Catalog\Model\Product\Image\UrlBuilder; use Magento\Framework\Data\Collection; use Magento\Framework\DataObject; use Magento\Framework\App\ObjectManager; @@ -47,13 +48,19 @@ class Gallery extends AbstractView */ private $galleryImagesConfigFactory; + /** + * @var UrlBuilder + */ + private $imageUrlBuilder; + /** * @param Context $context * @param ArrayUtils $arrayUtils * @param EncoderInterface $jsonEncoder * @param array $data - * @param ImagesConfigFactoryInterface $imagesConfigFactory + * @param ImagesConfigFactoryInterface|null $imagesConfigFactory * @param array $galleryImagesConfig + * @param UrlBuilder|null $urlBuilder */ public function __construct( Context $context, @@ -61,13 +68,15 @@ public function __construct( EncoderInterface $jsonEncoder, array $data = [], ImagesConfigFactoryInterface $imagesConfigFactory = null, - array $galleryImagesConfig = [] + array $galleryImagesConfig = [], + UrlBuilder $urlBuilder = null ) { parent::__construct($context, $arrayUtils, $data); $this->jsonEncoder = $jsonEncoder; $this->galleryImagesConfigFactory = $imagesConfigFactory ?: ObjectManager::getInstance() ->get(ImagesConfigFactoryInterface::class); $this->galleryImagesConfig = $galleryImagesConfig; + $this->imageUrlBuilder = $urlBuilder ?? ObjectManager::getInstance()->get(UrlBuilder::class); } /** @@ -88,9 +97,7 @@ public function getGalleryImages() foreach ($galleryImagesConfig as $imageConfig) { $image->setData( $imageConfig->getData('data_object_key'), - $this->_imageHelper->init($product, $imageConfig['image_id']) - ->setImageFile($image->getData('file')) - ->getUrl() + $this->imageUrlBuilder->getUrl($image->getFile(), $imageConfig['image_id']) ); } } diff --git a/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php b/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php deleted file mode 100644 index 7136c60edc18..000000000000 --- a/app/code/Magento/Catalog/Console/Command/ImagesResizeCommand.php +++ /dev/null @@ -1,99 +0,0 @@ -appState = $appState; - $this->productCollectionFactory = $productCollectionFactory; - $this->productRepository = $productRepository; - $this->imageCacheFactory = $imageCacheFactory; - parent::__construct(); - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this->setName('catalog:images:resize') - ->setDescription('Creates resized product images'); - } - - /** - * {@inheritdoc} - */ - protected function execute( - \Symfony\Component\Console\Input\InputInterface $input, - \Symfony\Component\Console\Output\OutputInterface $output - ) { - $this->appState->setAreaCode(\Magento\Framework\App\Area::AREA_GLOBAL); - - /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ - $productCollection = $this->productCollectionFactory->create(); - $productIds = $productCollection->getAllIds(); - if (!count($productIds)) { - $output->writeln("No product images to resize"); - return \Magento\Framework\Console\Cli::RETURN_SUCCESS; - } - - try { - foreach ($productIds as $productId) { - try { - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->productRepository->getById($productId); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - continue; - } - - /** @var \Magento\Catalog\Model\Product\Image\Cache $imageCache */ - $imageCache = $this->imageCacheFactory->create(); - $imageCache->generate($product); - - $output->write("."); - } - } catch (\Exception $e) { - $output->writeln("{$e->getMessage()}"); - // we must have an exit code higher than zero to indicate something was wrong - return \Magento\Framework\Console\Cli::RETURN_FAILURE; - } - - $output->write("\n"); - $output->writeln("Product images resized successfully"); - } -} diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php index 380fd0298c2d..4f128d639b2b 100644 --- a/app/code/Magento/Catalog/Helper/Image.php +++ b/app/code/Magento/Catalog/Helper/Image.php @@ -845,10 +845,10 @@ public function getHeight() public function getFrame() { $frame = $this->getAttribute('frame'); - if (empty($frame)) { + if ($frame === null) { $frame = $this->getConfigView()->getVarValue('Magento_Catalog', 'product_image_white_borders'); } - return $frame; + return (bool)$frame; } /** diff --git a/app/code/Magento/Catalog/Model/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php index 7888d8de1c2f..d2c11a376296 100644 --- a/app/code/Magento/Catalog/Model/ImageExtractor.php +++ b/app/code/Magento/Catalog/Model/ImageExtractor.php @@ -37,7 +37,7 @@ public function process(\DOMElement $mediaNode, $mediaParentTag) } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') { $nodeValue = intval($attribute->nodeValue); } else { - $nodeValue = $attribute->nodeValue; + $nodeValue = !in_array($attribute->nodeValue, ['false', '0']); } $result[$mediaParentTag][$moduleNameImage][Image::MEDIA_TYPE_CONFIG_NODE][$imageId][$attribute->tagName] = $nodeValue; diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 97ba145445b4..e58c9aab7766 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -946,13 +946,6 @@ public function afterSave() $this->_getResource()->addCommitCallback([$this, 'reindex']); $this->reloadPriceInfo(); - // Resize images for catalog product and save results to image cache - /** @var Product\Image\Cache $imageCache */ - if (!$this->_appState->isAreaCodeEmulated()) { - $imageCache = $this->imageCacheFactory->create(); - $imageCache->generate($this); - } - return $result; } @@ -1488,10 +1481,14 @@ public function getMediaAttributeValues() public function getMediaGalleryImages() { $directory = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA); - if (!$this->hasData('media_gallery_images') && is_array($this->getMediaGallery('images'))) { - $images = $this->_collectionFactory->create(); + if (!$this->hasData('media_gallery_images')) { + $this->setData('media_gallery_images', $this->_collectionFactory->create()); + } + if (!$this->getData('media_gallery_images')->count() && is_array($this->getMediaGallery('images'))) { + $images = $this->getData('media_gallery_images'); foreach ($this->getMediaGallery('images') as $image) { - if ((isset($image['disabled']) && $image['disabled']) + if (!empty($image['disabled']) + || !empty($image['removed']) || empty($image['value_id']) || $images->getItemById($image['value_id']) != null ) { diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 971f34e02f9e..09ba68ddbe2b 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -6,10 +6,13 @@ namespace Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Image\NotLoadInfoImageException; +use Magento\Catalog\Model\View\Asset\ImageFactory; +use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\Image as MagentoImage; use Magento\Framework\Serialize\SerializerInterface; +use Magento\Catalog\Model\Product\Image\ParamsBuilder; /** * @method string getFile() @@ -159,12 +162,12 @@ class Image extends \Magento\Framework\Model\AbstractModel protected $_storeManager; /** - * @var \Magento\Catalog\Model\View\Asset\ImageFactory + * @var ImageFactory */ private $viewAssetImageFactory; /** - * @var \Magento\Catalog\Model\View\Asset\PlaceholderFactory + * @var PlaceholderFactory */ private $viewAssetPlaceholderFactory; @@ -173,6 +176,11 @@ class Image extends \Magento\Framework\Model\AbstractModel */ private $imageAsset; + /** + * @var ParamsBuilder + */ + private $paramsBuilder; + /** * @var string */ @@ -199,9 +207,10 @@ class Image extends \Magento\Framework\Model\AbstractModel * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data - * @param \Magento\Catalog\Model\View\Asset\ImageFactory|null $viewAssetImageFactory - * @param \Magento\Catalog\Model\View\Asset\PlaceholderFactory|null $viewAssetPlaceholderFactory + * @param ImageFactory|null $viewAssetImageFactory + * @param PlaceholderFactory|null $viewAssetPlaceholderFactory * @param SerializerInterface|null $serializer + * @param ParamsBuilder $paramsBuilder * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ @@ -215,13 +224,14 @@ public function __construct( \Magento\Framework\Image\Factory $imageFactory, \Magento\Framework\View\Asset\Repository $assetRepo, \Magento\Framework\View\FileSystem $viewFileSystem, + ImageFactory $viewAssetImageFactory, + PlaceholderFactory $viewAssetPlaceholderFactory, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - \Magento\Catalog\Model\View\Asset\ImageFactory $viewAssetImageFactory = null, - \Magento\Catalog\Model\View\Asset\PlaceholderFactory $viewAssetPlaceholderFactory = null, - SerializerInterface $serializer = null + SerializerInterface $serializer = null, + ParamsBuilder $paramsBuilder = null ) { $this->_storeManager = $storeManager; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; @@ -232,11 +242,10 @@ public function __construct( $this->_assetRepo = $assetRepo; $this->_viewFileSystem = $viewFileSystem; $this->_scopeConfig = $scopeConfig; - $this->viewAssetImageFactory = $viewAssetImageFactory ?: ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\View\Asset\ImageFactory::class); - $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory ?: ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\View\Asset\PlaceholderFactory::class); + $this->viewAssetImageFactory = $viewAssetImageFactory; + $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); + $this->paramsBuilder = $paramsBuilder ?: ObjectManager::getInstance()->get(ParamsBuilder::class); } /** @@ -370,25 +379,6 @@ public function setSize($size) return $this; } - /** - * Convert array of 3 items (decimal r, g, b) to string of their hex values - * - * @param int[] $rgbArray - * @return string - */ - protected function _rgbToString($rgbArray) - { - $result = []; - foreach ($rgbArray as $value) { - if (null === $value) { - $result[] = 'null'; - } else { - $result[] = sprintf('%02s', dechex($value)); - } - } - return implode($result); - } - /** * Set filenames for base file and new file * @@ -618,10 +608,8 @@ public function getDestinationSubdir() */ public function isCached() { - return ( - is_array($this->loadImageInfoFromCache($this->imageAsset->getPath())) || - file_exists($this->imageAsset->getPath()) - ); + $path = $this->imageAsset->getPath(); + return is_array($this->loadImageInfoFromCache($path)) || file_exists($path); } /** @@ -826,7 +814,7 @@ public function getResizedImageInfo() $image = $this->imageAsset->getPath(); } - $imageProperties = $this->getimagesize($image); + $imageProperties = $this->getImageSize($image); return $imageProperties; } finally { @@ -844,29 +832,20 @@ public function getResizedImageInfo() */ private function getMiscParams() { - $miscParams = [ - 'image_type' => $this->getDestinationSubdir(), - 'image_height' => $this->getHeight(), - 'image_width' => $this->getWidth(), - 'keep_aspect_ratio' => ($this->_keepAspectRatio ? '' : 'non') . 'proportional', - 'keep_frame' => ($this->_keepFrame ? '' : 'no') . 'frame', - 'keep_transparency' => ($this->_keepTransparency ? '' : 'no') . 'transparency', - 'constrain_only' => ($this->_constrainOnly ? 'do' : 'not') . 'constrainonly', - 'background' => $this->_rgbToString($this->_backgroundColor), - 'angle' => $this->_angle, - 'quality' => $this->_quality, - ]; - - // if has watermark add watermark params to hash - if ($this->getWatermarkFile()) { - $miscParams['watermark_file'] = $this->getWatermarkFile(); - $miscParams['watermark_image_opacity'] = $this->getWatermarkImageOpacity(); - $miscParams['watermark_position'] = $this->getWatermarkPosition(); - $miscParams['watermark_width'] = $this->getWatermarkWidth(); - $miscParams['watermark_height'] = $this->getWatermarkHeight(); - } - - return $miscParams; + return $this->paramsBuilder->build( + [ + 'type' => $this->getDestinationSubdir(), + 'width' => $this->getWidth(), + 'height' => $this->getHeight(), + 'frame' => $this->_keepFrame, + 'constrain' => $this->_constrainOnly, + 'aspect_ratio' => $this->_keepAspectRatio, + 'transparency' => $this->_keepTransparency, + 'background' => $this->_backgroundColor, + 'angle' => $this->_angle, + 'quality' => $this->_quality + ] + ); } /** diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php new file mode 100644 index 000000000000..dd8d352feceb --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php @@ -0,0 +1,165 @@ +scopeConfig = $scopeConfig; + $this->viewConfig = $viewConfig; + } + + /** + * @param array $imageArguments + * @return array + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function build(array $imageArguments): array + { + $miscParams = [ + 'image_type' => $imageArguments['type'] ?? null, + 'image_height' => $imageArguments['height'] ?? null, + 'image_width' => $imageArguments['width'] ?? null, + ]; + + $overwritten = $this->overwriteDefaultValues($imageArguments); + $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type']) : []; + + return array_merge($miscParams, $overwritten, $watermark); + } + + /** + * @param array $imageArguments + * @return array + */ + private function overwriteDefaultValues(array $imageArguments): array + { + $frame = $imageArguments['frame'] ?? $this->hasDefaultFrame(); + $constrain = $imageArguments['constrain'] ?? $this->defaultConstrainOnly; + $aspectRatio = $imageArguments['aspect_ratio'] ?? $this->defaultKeepAspectRatio; + $transparency = $imageArguments['transparency'] ?? $this->defaultKeepTransparency; + $background = $imageArguments['background'] ?? $this->defaultBackground; + $angle = $imageArguments['angle'] ?? $this->defaultAngle; + + return [ + 'background' => (array) $background, + 'angle' => $angle, + 'quality' => $this->defaultQuality, + 'keep_aspect_ratio' => (bool) $aspectRatio, + 'keep_frame' => (bool) $frame, + 'keep_transparency' => (bool) $transparency, + 'constrain_only' => (bool) $constrain, + ]; + } + + /** + * @param string $type + * @return array + */ + private function getWatermark(string $type): array + { + $file = $this->scopeConfig->getValue( + "design/watermark/{$type}_image", + ScopeInterface::SCOPE_STORE + ); + + if ($file) { + $size = $this->scopeConfig->getValue( + "design/watermark/{$type}_size", + ScopeInterface::SCOPE_STORE + ); + $opacity = $this->scopeConfig->getValue( + "design/watermark/{$type}_imageOpacity", + ScopeInterface::SCOPE_STORE + ); + $position = $this->scopeConfig->getValue( + "design/watermark/{$type}_position", + ScopeInterface::SCOPE_STORE + ); + $width = !empty($size['width']) ? $size['width'] : null; + $height = !empty($size['height']) ? $size['height'] : null; + + return [ + 'watermark_file' => $file, + 'watermark_image_opacity' => $opacity, + 'watermark_position' => $position, + 'watermark_width' => $width, + 'watermark_height' => $height + ]; + } + + return []; + } + + /** + * Get frame from product_image_white_borders + * @return bool + */ + private function hasDefaultFrame(): bool + { + return (bool) $this->viewConfig->getViewConfig()->getVarValue( + 'Magento_Catalog', + 'product_image_white_borders' + ); + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Image/UrlBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/UrlBuilder.php new file mode 100644 index 000000000000..a5fdc3b0a6ea --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Image/UrlBuilder.php @@ -0,0 +1,92 @@ +presentationConfig = $presentationConfig; + $this->imageParamsBuilder = $imageParamsBuilder; + $this->viewAssetImageFactory = $viewAssetImageFactory; + $this->placeholderFactory = $placeholderFactory; + } + + /** + * Build image url using base path and params + * + * @param string $baseFilePath + * @param string $imageDisplayArea + * @return string + */ + public function getUrl(string $baseFilePath, string $imageDisplayArea): string + { + $imageArguments = $this->presentationConfig->getViewConfig()->getMediaAttributes( + 'Magento_Catalog', + Image::MEDIA_TYPE_CONFIG_NODE, + $imageDisplayArea + ); + + $imageMiscParams = $this->imageParamsBuilder->build($imageArguments); + + if ($baseFilePath === null || $baseFilePath === 'no_selection') { + $asset = $this->placeholderFactory->create( + [ + 'type' => $imageMiscParams['image_type'] + ] + ); + } else { + $asset = $this->viewAssetImageFactory->create( + [ + 'miscParams' => $imageMiscParams, + 'filePath' => $baseFilePath, + ] + ); + } + + return $asset->getUrl(); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php new file mode 100644 index 000000000000..123f358be40c --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php @@ -0,0 +1,97 @@ +batchQueryGenerator = $generator; + $this->resourceConnection = $resourceConnection; + $this->connection = $this->resourceConnection->getConnection(); + $this->batchSize = $batchSize; + } + + /** + * Returns product images + * + * @return \Generator + */ + public function getAllProductImages(): \Generator + { + $batchSelectIterator = $this->batchQueryGenerator->generate( + 'value_id', + $this->getVisibleImagesSelect(), + $this->batchSize, + \Magento\Framework\DB\Query\BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR + ); + + foreach ($batchSelectIterator as $select) { + foreach ($this->connection->fetchAll($select) as $key => $value) { + yield $key => $value; + } + } + } + + /** + * Get the number of unique pictures of products + * @return int + */ + public function getCountAllProductImages(): int + { + $select = $this->getVisibleImagesSelect()->reset('columns')->columns('count(*)'); + return (int) $this->connection->fetchOne($select); + } + + /** + * @return Select + */ + private function getVisibleImagesSelect(): Select + { + return $this->connection->select()->distinct() + ->from( + ['images' => $this->resourceConnection->getTableName(Gallery::GALLERY_TABLE)], + 'value as filepath' + )->where( + 'disabled = 0' + ); + } +} diff --git a/app/code/Magento/Catalog/Model/View/Asset/Image.php b/app/code/Magento/Catalog/Model/View/Asset/Image.php index ce85ba21d211..dfae9f4b0da9 100644 --- a/app/code/Magento/Catalog/Model/View/Asset/Image.php +++ b/app/code/Magento/Catalog/Model/View/Asset/Image.php @@ -19,6 +19,13 @@ */ class Image implements LocalInterface { + /** + * Image type of image (thumbnail,small_image,image,swatch_image,swatch_thumb) + * + * @var string + */ + private $sourceContentType; + /** * @var string */ @@ -65,8 +72,14 @@ public function __construct( ContextInterface $context, EncryptorInterface $encryptor, $filePath, - array $miscParams = [] + array $miscParams ) { + if (isset($miscParams['image_type'])) { + $this->sourceContentType = $miscParams['image_type']; + unset($miscParams['image_type']); + } else { + $this->sourceContentType = $this->contentType; + } $this->mediaConfig = $mediaConfig; $this->context = $context; $this->filePath = $filePath; @@ -79,7 +92,7 @@ public function __construct( */ public function getUrl() { - return $this->context->getBaseUrl() . $this->getRelativePath(DIRECTORY_SEPARATOR); + return $this->context->getBaseUrl() . DIRECTORY_SEPARATOR . $this->getImageInfo(); } /** @@ -95,22 +108,7 @@ public function getContentType() */ public function getPath() { - return $this->getAbsolutePath($this->context->getPath()); - } - - /** - * Subroutine for building path - * - * @param string $path - * @param string $item - * @return string - */ - private function join($path, $item) - { - return trim( - $path . ($item ? DIRECTORY_SEPARATOR . ltrim($item, DIRECTORY_SEPARATOR) : ''), - DIRECTORY_SEPARATOR - ); + return $this->context->getPath() . DIRECTORY_SEPARATOR . $this->getImageInfo(); } /** @@ -119,7 +117,7 @@ private function join($path, $item) public function getSourceFile() { return $this->mediaConfig->getBaseMediaPath() - . DIRECTORY_SEPARATOR . ltrim($this->filePath, DIRECTORY_SEPARATOR); + . DIRECTORY_SEPARATOR . ltrim($this->getFilePath(), DIRECTORY_SEPARATOR); } /** @@ -129,7 +127,7 @@ public function getSourceFile() */ public function getSourceContentType() { - return $this->contentType; + return $this->sourceContentType; } /** @@ -172,35 +170,43 @@ public function getModule() */ private function getMiscPath() { - return $this->encryptor->hash(implode('_', $this->miscParams), Encryptor::HASH_VERSION_MD5); + return $this->encryptor->hash( + implode('_', $this->convertToReadableFormat($this->miscParams)), + Encryptor::HASH_VERSION_MD5 + ); } /** - * Generate absolute path + * Generate path from image info * - * @param string $result * @return string */ - private function getAbsolutePath($result) + private function getImageInfo() { - $prefix = (substr($result, 0, 1) == DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : ''; - $result = $this->join($result, $this->getModule()); - $result = $this->join($result, $this->getMiscPath()); - $result = $this->join($result, $this->getFilePath()); - return $prefix . $result; + $path = $this->getModule() + . DIRECTORY_SEPARATOR . $this->getMiscPath() + . DIRECTORY_SEPARATOR . $this->getFilePath(); + return preg_replace('|\Q'. DIRECTORY_SEPARATOR . '\E+|', DIRECTORY_SEPARATOR, $path); } /** - * Generate relative path - * - * @param string $result - * @return string + * Converting bool into a string representation + * @param $miscParams + * @return array */ - private function getRelativePath($result) + private function convertToReadableFormat($miscParams) { - $result = $this->join($result, $this->getModule()); - $result = $this->join($result, $this->getMiscPath()); - $result = $this->join($result, $this->getFilePath()); - return DIRECTORY_SEPARATOR . $result; + $miscParams['image_height'] = 'h:' . ($miscParams['image_height'] ?? 'empty'); + $miscParams['image_width'] = 'w:' . ($miscParams['image_width'] ?? 'empty'); + $miscParams['quality'] = 'q:' . ($miscParams['quality'] ?? 'empty'); + $miscParams['angle'] = 'r:' . ($miscParams['angle'] ?? 'empty'); + $miscParams['keep_aspect_ratio'] = (isset($miscParams['keep_aspect_ratio']) ? '' : 'non') . 'proportional'; + $miscParams['keep_frame'] = (isset($miscParams['keep_frame']) ? '' : 'no') . 'frame'; + $miscParams['keep_transparency'] = (isset($miscParams['keep_transparency']) ? '' : 'no') . 'transparency'; + $miscParams['constrain_only'] = (isset($miscParams['constrain_only']) ? 'do' : 'not') . 'constrainonly'; + $miscParams['background'] = isset($miscParams['background']) + ? 'rgb' . implode(',', $miscParams['background']) + : 'nobackground'; + return $miscParams; } } diff --git a/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php b/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php index 5b9f6d33d3aa..49d150a31750 100644 --- a/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php +++ b/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php @@ -37,6 +37,8 @@ class Context implements ContextInterface private $filesystem; /** + * @param \Magento\Catalog\Model\Product\Media\ConfigInterface $mediaConfig + * @param \Magento\Framework\Filesystem $filesystem */ public function __construct( \Magento\Catalog\Model\Product\Media\ConfigInterface $mediaConfig, diff --git a/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php b/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php new file mode 100644 index 000000000000..91d2868afab8 --- /dev/null +++ b/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php @@ -0,0 +1,69 @@ +imageResize = $imageResize; + $this->state = $state; + } + + /** + * Process event on 'save_commit_after' event + * + * @param \Magento\Framework\Event\Observer $observer + * @return void + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + /** @var $product \Magento\Catalog\Model\Product */ + $product = $observer->getEvent()->getProduct(); + + if ($this->state->isAreaCodeEmulated()) { + return; + } + + if (!(bool) $product->getId()) { + foreach ($product->getMediaGalleryImages() as $image) { + $this->imageResize->resizeFromImageName($image->getFile()); + } + } else { + $new = $product->getData('media_gallery'); + $original = $product->getOrigData('media_gallery'); + $mediaGallery = !empty($new['images']) ? array_column($new['images'], 'file') : []; + $mediaOriginalGallery = !empty($original['images']) ? array_column($original['images'], 'file') : []; + + foreach (array_diff($mediaGallery, $mediaOriginalGallery) as $image) { + $this->imageResize->resizeFromImageName($image); + } + } + } +} diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredOptions.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredOptions.php new file mode 100644 index 000000000000..f96d56cb8818 --- /dev/null +++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredOptions.php @@ -0,0 +1,47 @@ +getProduct(); + $value = 0.; + $optionIds = $item->getOptionByCode('option_ids'); + if ($optionIds) { + foreach (explode(',', $optionIds->getValue()) as $optionId) { + $option = $product->getOptionById($optionId); + if ($option !== null) { + $itemOption = $item->getOptionByCode('option_' . $option->getId()); + /** @var $group \Magento\Catalog\Model\Product\Option\Type\DefaultType */ + $group = $option->groupFactory($option->getType()) + ->setOption($option) + ->setConfigurationItem($item) + ->setConfigurationItemOption($itemOption); + $value += $group->getOptionPrice($itemOption->getValue(), $basePrice); + } + } + } + + return $value; + } +} diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredPrice.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredPrice.php index 87d031d8d5b3..6ec282e45a1a 100644 --- a/app/code/Magento/Catalog/Pricing/Price/ConfiguredPrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredPrice.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; use Magento\Framework\Pricing\Adjustment\CalculatorInterface; +use Magento\Framework\App\ObjectManager; /** * Configured price model @@ -25,21 +26,29 @@ class ConfiguredPrice extends FinalPrice implements ConfiguredPriceInterface */ protected $item; + /** + * @var ConfiguredOptions + */ + private $configuredOptions; + /** * @param Product $saleableItem * @param float $quantity * @param CalculatorInterface $calculator * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency - * @param ItemInterface $item + * @param ItemInterface|null $item + * @param ConfiguredOptions|null $configuredOptions */ public function __construct( Product $saleableItem, $quantity, CalculatorInterface $calculator, \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, - ItemInterface $item = null + ItemInterface $item = null, + ConfiguredOptions $configuredOptions = null ) { $this->item = $item; + $this->configuredOptions = $configuredOptions ?: ObjectManager::getInstance()->get(ConfiguredOptions::class); parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); } @@ -54,11 +63,12 @@ public function setItem(ItemInterface $item) } /** - * Get value of configured options + * Get value of configured options. * - * @return array + * @deprecated ConfiguredOptions::getItemOptionsValue is used instead + * @return float */ - protected function getOptionsValue() + protected function getOptionsValue(): float { $product = $this->item->getProduct(); $value = 0.; @@ -78,16 +88,21 @@ protected function getOptionsValue() } } } + return $value; } /** - * Price value of product with configured options + * Price value of product with configured options. * * @return bool|float */ public function getValue() { - return $this->item ? parent::getValue() + $this->getOptionsValue() : parent::getValue(); + $basePrice = parent::getValue(); + + return $this->item + ? $basePrice + $this->configuredOptions->getItemOptionsValue($basePrice, $this->item) + : $basePrice; } } diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceInterface.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceInterface.php index e6d35a0f5239..e41df30ea1de 100644 --- a/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceInterface.php +++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceInterface.php @@ -9,15 +9,20 @@ use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; /** - * Configured price interface + * Configured price interface. */ interface ConfiguredPriceInterface { /** - * Price type configured + * Price type configured. */ const CONFIGURED_PRICE_CODE = 'configured_price'; + /** + * Regular price type configured. + */ + const CONFIGURED_REGULAR_PRICE_CODE = 'configured_regular_price'; + /** * @param ItemInterface $item * @return $this diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceSelection.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceSelection.php new file mode 100644 index 000000000000..1dedbfa35446 --- /dev/null +++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredPriceSelection.php @@ -0,0 +1,64 @@ +calculator = $calculator; + } + + /** + * Get Selection pricing list. + * + * @param ConfiguredPriceInterface $price + * @return array + */ + public function getSelectionPriceList(ConfiguredPriceInterface $price): array + { + $selectionPriceList = []; + foreach ($price->getOptions() as $option) { + $selectionPriceList = array_merge( + $selectionPriceList, + $this->createSelectionPriceList($option, $price->getProduct()) + ); + } + + return $selectionPriceList; + } + + /** + * Create Selection Price List. + * + * @param ExtensibleDataInterface $option + * @param Product $product + * @return array + */ + private function createSelectionPriceList(ExtensibleDataInterface $option, Product $product): array + { + return $this->calculator->createSelectionPriceList($option, $product); + } +} diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php new file mode 100644 index 000000000000..bcb6638b9cd2 --- /dev/null +++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php @@ -0,0 +1,80 @@ +item = $item; + $this->configuredOptions = $configuredOptions; + parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); + } + + /** + * @param ItemInterface $item + * @return $this + */ + public function setItem(ItemInterface $item) : ConfiguredRegularPrice + { + $this->item = $item; + + return $this; + } + + /** + * Price value of product with configured options. + * + * @return bool|float + */ + public function getValue() + { + $basePrice = parent::getValue(); + + return $this->item + ? $basePrice + $this->configuredOptions->getItemOptionsValue($basePrice, $this->item) + : $basePrice; + } +} diff --git a/app/code/Magento/Catalog/Pricing/Render/ConfiguredPriceBox.php b/app/code/Magento/Catalog/Pricing/Render/ConfiguredPriceBox.php index 0722f018ae4e..91b2b6d2a0d7 100644 --- a/app/code/Magento/Catalog/Pricing/Render/ConfiguredPriceBox.php +++ b/app/code/Magento/Catalog/Pricing/Render/ConfiguredPriceBox.php @@ -7,12 +7,62 @@ namespace Magento\Catalog\Pricing\Render; use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface; +use Magento\Catalog\Pricing\Price\MinimalPriceCalculatorInterface; +use Magento\Framework\Pricing\Price\PriceInterface; +use Magento\Catalog\Pricing\Price\ConfiguredPriceInterface; +use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\Catalog\Pricing\Price\RegularPrice; +use Magento\Framework\Pricing\Render\RendererPool; +use Magento\Framework\Pricing\SaleableInterface; +use Magento\Framework\View\Element\Template\Context; +use Magento\Catalog\Pricing\Price\ConfiguredPriceSelection; +use Magento\Framework\App\ObjectManager; /** - * Class for configured_price rendering + * Class for configured_price rendering. */ class ConfiguredPriceBox extends FinalPriceBox { + /** + * @var ConfiguredPriceSelection + */ + private $configuredPriceSelection; + + /** + * @param Context $context + * @param SaleableInterface $saleableItem + * @param PriceInterface $price + * @param RendererPool $rendererPool + * @param array $data + * @param SalableResolverInterface|null $salableResolver + * @param MinimalPriceCalculatorInterface|null $minimalPriceCalculator + * @param ConfiguredPriceSelection|null $configuredPriceSelection + */ + public function __construct( + Context $context, + SaleableInterface $saleableItem, + PriceInterface $price, + RendererPool $rendererPool, + array $data = [], + SalableResolverInterface $salableResolver = null, + MinimalPriceCalculatorInterface $minimalPriceCalculator = null, + ConfiguredPriceSelection $configuredPriceSelection = null + ) { + $this->configuredPriceSelection = $configuredPriceSelection + ?: ObjectManager::getInstance() + ->get(ConfiguredPriceSelection::class); + parent::__construct( + $context, + $saleableItem, + $price, + $rendererPool, + $data, + $salableResolver, + $minimalPriceCalculator + ); + } + /** * Retrieve an item instance to the configured price model * @@ -34,4 +84,67 @@ protected function _prepareLayout() } return parent::_prepareLayout(); } + + /** + * {@inheritdoc} + */ + public function getPriceType($priceCode) + { + $price = $this->saleableItem->getPriceInfo()->getPrice($priceCode); + $item = $this->getData('item'); + if ($price instanceof ConfiguredPriceInterface + && $item instanceof ItemInterface + ) { + $price->setItem($item); + } + + return $price; + } + + /** + * @return PriceInterface + */ + public function getConfiguredPrice(): PriceInterface + { + /** @var \Magento\Bundle\Pricing\Price\ConfiguredPrice $configuredPrice */ + $configuredPrice = $this->getPrice(); + if (empty($this->configuredPriceSelection->getSelectionPriceList($configuredPrice))) { + // If there was no selection we must show minimal regular price + return $this->getSaleableItem()->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE); + } + + return $configuredPrice; + } + + /** + * @return PriceInterface + */ + public function getConfiguredRegularPrice(): PriceInterface + { + /** @var \Magento\Bundle\Pricing\Price\ConfiguredPrice $configuredPrice */ + $configuredPrice = $this->getPriceType(ConfiguredPriceInterface::CONFIGURED_REGULAR_PRICE_CODE); + if (empty($this->configuredPriceSelection->getSelectionPriceList($configuredPrice))) { + // If there was no selection we must show minimal regular price + return $this->getSaleableItem()->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE); + } + + return $configuredPrice; + } + + /** + * Define if the special price should be shown. + * + * @return bool + */ + public function hasSpecialPrice(): bool + { + if ($this->price->getPriceCode() == ConfiguredPriceInterface::CONFIGURED_PRICE_CODE) { + $displayRegularPrice = $this->getConfiguredRegularPrice()->getAmount()->getValue(); + $displayFinalPrice = $this->getConfiguredPrice()->getAmount()->getValue(); + + return $displayFinalPrice < $displayRegularPrice; + } + + return parent::hasSpecialPrice(); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/AbstractProductTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/AbstractProductTest.php index 36bd6abea761..d003c6d01373 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/AbstractProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/AbstractProductTest.php @@ -6,6 +6,10 @@ namespace Magento\Catalog\Test\Unit\Block\Product; +use Magento\Catalog\Block\Product\Image; +use Magento\Catalog\Block\Product\ImageBuilder; +use Magento\Catalog\Model\Product; + /** * Class for testing methods of AbstractProduct */ @@ -32,7 +36,7 @@ class AbstractProductTest extends \PHPUnit\Framework\TestCase protected $stockRegistryMock; /** - * @var \Magento\Catalog\Block\Product\ImageBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var ImageBuilder|\PHPUnit_Framework_MockObject_MockObject */ protected $imageBuilder; @@ -58,9 +62,7 @@ protected function setUp() ['getStockItem'] ); - $this->imageBuilder = $this->getMockBuilder(\Magento\Catalog\Block\Product\ImageBuilder::class) - ->disableOriginalConstructor() - ->getMock(); + $this->imageBuilder = $this->createPartialMock(ImageBuilder::class, ['create']); $this->productContextMock->expects($this->once()) ->method('getStockRegistry') @@ -88,7 +90,7 @@ public function testGetProductPrice() { $expectedPriceHtml = 'Expected Price html with price $30'; $priceRenderBlock = $this->createPartialMock(\Magento\Framework\Pricing\Render::class, ['render']); - $product = $this->createMock(\Magento\Catalog\Model\Product::class); + $product = $this->createMock(Product::class); $this->layoutMock->expects($this->once()) ->method('getBlock') @@ -108,7 +110,7 @@ public function testGetProductPriceHtml() { $expectedPriceHtml = 'Expected Price html with price $30'; $priceRenderBlock = $this->createPartialMock(\Magento\Framework\Pricing\Render::class, ['render']); - $product = $this->createMock(\Magento\Catalog\Model\Product::class); + $product = $this->createMock(Product::class); $this->layoutMock->expects($this->once()) ->method('getBlock') @@ -139,7 +141,7 @@ public function testGetMinimalQty($minSale, $result) $id = 10; $websiteId = 99; - $productMock = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['getId', 'getStore']); + $productMock = $this->createPartialMock(Product::class, ['getId', 'getStore']); $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getWebsiteId']); $stockItemMock = $this->getMockForAbstractClass( \Magento\CatalogInventory\Api\Data\StockItemInterface::class, @@ -168,7 +170,7 @@ public function testGetMinimalQty($minSale, $result) ->method('getMinSaleQty') ->will($this->returnValue($minSale)); - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $productMock */ + /** @var Product|\PHPUnit_Framework_MockObject_MockObject $productMock */ $this->assertEquals($result, $this->block->getMinimalQty($productMock)); } @@ -195,34 +197,14 @@ public function testGetImage() { $imageId = 'test_image_id'; $attributes = []; - - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $imageMock = $this->getMockBuilder(\Magento\Catalog\Block\Product\Image::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->imageBuilder->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $this->imageBuilder->expects($this->once()) - ->method('setImageId') - ->with($imageId) - ->willReturnSelf(); - $this->imageBuilder->expects($this->once()) - ->method('setAttributes') - ->with($attributes) - ->willReturnSelf(); - $this->imageBuilder->expects($this->once()) + $productMock = $this->createMock(Product::class); + $imageMock = $this->createMock(Image::class); + $this->imageBuilder->expects(static::once()) ->method('create') ->willReturn($imageMock); - $this->assertInstanceOf( - \Magento\Catalog\Block\Product\Image::class, - $this->block->getImage($productMock, $imageId, $attributes) - ); + $image = $this->block->getImage($productMock, $imageId, $attributes); + + static::assertInstanceOf(Image::class, $image); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageBuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageBuilderTest.php deleted file mode 100644 index 3094b423a4be..000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageBuilderTest.php +++ /dev/null @@ -1,295 +0,0 @@ -helperFactory = $this->createPartialMock(\Magento\Catalog\Helper\ImageFactory::class, ['create']); - - $this->imageFactory = $this->createPartialMock(ImageFactory::class, ['create']); - - $this->model = new ImageBuilder($this->helperFactory, $this->imageFactory); - } - - public function testSetProduct() - { - $productMock = $this->createMock(Product::class); - - $this->assertInstanceOf( - ImageBuilder::class, - $this->model->setProduct($productMock) - ); - } - - public function testSetImageId() - { - $imageId = 'test_image_id'; - - $this->assertInstanceOf( - ImageBuilder::class, - $this->model->setImageId($imageId) - ); - } - - public function testSetAttributes() - { - $attributes = [ - 'name' => 'value', - ]; - $this->assertInstanceOf( - ImageBuilder::class, - $this->model->setAttributes($attributes) - ); - } - - /** - * @param array $data - * @dataProvider createDataProvider - */ - public function testCreate($data, $expected) - { - $imageId = 'test_image_id'; - - $productMock = $this->createMock(Product::class); - - $helperMock = $this->createMock(Image::class); - $helperMock->expects($this->once()) - ->method('init') - ->with($productMock, $imageId) - ->willReturnSelf(); - - $helperMock->expects($this->once()) - ->method('getFrame') - ->willReturn($data['frame']); - $helperMock->expects($this->once()) - ->method('getUrl') - ->willReturn($data['url']); - $helperMock->expects($this->exactly(2)) - ->method('getWidth') - ->willReturn($data['width']); - $helperMock->expects($this->exactly(2)) - ->method('getHeight') - ->willReturn($data['height']); - $helperMock->expects($this->once()) - ->method('getLabel') - ->willReturn($data['label']); - $helperMock->expects($this->once()) - ->method('getResizedImageInfo') - ->willReturn($data['imagesize']); - - $this->helperFactory->expects($this->once()) - ->method('create') - ->willReturn($helperMock); - - $imageMock = $this->createMock(\Magento\Catalog\Block\Product\Image::class); - - $this->imageFactory->expects($this->once()) - ->method('create') - ->with($expected) - ->willReturn($imageMock); - - $this->model->setProduct($productMock); - $this->model->setImageId($imageId); - $this->model->setAttributes($data['custom_attributes']); - $this->assertInstanceOf(\Magento\Catalog\Block\Product\Image::class, $this->model->create()); - } - - /** - * Check if custom attributes will be overridden when builder used few times - * @param array $data - * @dataProvider createMultipleCallsDataProvider - */ - public function testCreateMultipleCalls($data) - { - list ($firstCall, $secondCall) = array_values($data); - - $imageId = 'test_image_id'; - - $productMock = $this->createMock(Product::class); - - $helperMock = $this->createMock(Image::class); - $helperMock->expects($this->exactly(2)) - ->method('init') - ->with($productMock, $imageId) - ->willReturnSelf(); - - $helperMock->expects($this->exactly(2)) - ->method('getFrame') - ->willReturnOnConsecutiveCalls($firstCall['data']['frame'], $secondCall['data']['frame']); - $helperMock->expects($this->exactly(2)) - ->method('getUrl') - ->willReturnOnConsecutiveCalls($firstCall['data']['url'], $secondCall['data']['url']); - $helperMock->expects($this->exactly(4)) - ->method('getWidth') - ->willReturnOnConsecutiveCalls( - $firstCall['data']['width'], - $firstCall['data']['width'], - $secondCall['data']['width'], - $secondCall['data']['width'] - ); - $helperMock->expects($this->exactly(4)) - ->method('getHeight') - ->willReturnOnConsecutiveCalls( - $firstCall['data']['height'], - $firstCall['data']['height'], - $secondCall['data']['height'], - $secondCall['data']['height'] - ); - $helperMock->expects($this->exactly(2)) - ->method('getLabel') - ->willReturnOnConsecutiveCalls($firstCall['data']['label'], $secondCall['data']['label']); - $helperMock->expects($this->exactly(2)) - ->method('getResizedImageInfo') - ->willReturnOnConsecutiveCalls($firstCall['data']['imagesize'], $secondCall['data']['imagesize']); - $this->helperFactory->expects($this->exactly(2)) - ->method('create') - ->willReturn($helperMock); - - $imageMock = $this->createMock(\Magento\Catalog\Block\Product\Image::class); - - $this->imageFactory->expects($this->at(0)) - ->method('create') - ->with($firstCall['expected']) - ->willReturn($imageMock); - - $this->imageFactory->expects($this->at(1)) - ->method('create') - ->with($secondCall['expected']) - ->willReturn($imageMock); - - $this->model->setProduct($productMock); - $this->model->setImageId($imageId); - $this->model->setAttributes($firstCall['data']['custom_attributes']); - - $this->assertInstanceOf(\Magento\Catalog\Block\Product\Image::class, $this->model->create()); - - $this->model->setProduct($productMock); - $this->model->setImageId($imageId); - $this->model->setAttributes($secondCall['data']['custom_attributes']); - $this->assertInstanceOf(\Magento\Catalog\Block\Product\Image::class, $this->model->create()); - } - - /** - * @return array - */ - public function createDataProvider(): array - { - return [ - $this->getTestDataWithoutAttributes(), - $this->getTestDataWithAttributes(), - ]; - } - - /** - * @return array - */ - public function createMultipleCallsDataProvider(): array - { - return [ - [ - [ - 'without_attributes' => $this->getTestDataWithoutAttributes(), - 'with_attributes' => $this->getTestDataWithAttributes(), - ], - ], - [ - [ - 'with_attributes' => $this->getTestDataWithAttributes(), - 'without_attributes' => $this->getTestDataWithoutAttributes(), - ], - ], - ]; - } - - /** - * @return array - */ - private function getTestDataWithoutAttributes(): array - { - return [ - 'data' => [ - 'frame' => 0, - 'url' => 'test_url_1', - 'width' => 100, - 'height' => 100, - 'label' => 'test_label', - 'custom_attributes' => [], - 'imagesize' => [100, 100], - ], - 'expected' => [ - 'data' => [ - 'template' => 'Magento_Catalog::product/image_with_borders.phtml', - 'image_url' => 'test_url_1', - 'width' => 100, - 'height' => 100, - 'label' => 'test_label', - 'ratio' => 1, - 'custom_attributes' => '', - 'resized_image_width' => 100, - 'resized_image_height' => 100, - 'product_id' => null - ], - ], - ]; - } - - /** - * @return array - */ - private function getTestDataWithAttributes(): array - { - return [ - 'data' => [ - 'frame' => 1, - 'url' => 'test_url_2', - 'width' => 100, - 'height' => 50, - 'label' => 'test_label_2', - 'custom_attributes' => [ - 'name_1' => 'value_1', - 'name_2' => 'value_2', - ], - 'imagesize' => [120, 70], - ], - 'expected' => [ - 'data' => [ - 'template' => 'Magento_Catalog::product/image.phtml', - 'image_url' => 'test_url_2', - 'width' => 100, - 'height' => 50, - 'label' => 'test_label_2', - 'ratio' => 0.5, - 'custom_attributes' => 'name_1="value_1" name_2="value_2"', - 'resized_image_width' => 120, - 'resized_image_height' => 70, - 'product_id' => null - ], - ], - ]; - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php new file mode 100644 index 000000000000..8a42865a3fe4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php @@ -0,0 +1,209 @@ +viewConfig = $this->createMock(View::class); + $configInterface = $this->createMock(ConfigInterface::class); + $configInterface->method('getViewConfig')->willReturn($this->viewConfig); + $this->viewAssetImageFactory = $this->createMock(ViewAssetImageFactory::class); + $this->paramsBuilder = $this->createMock(ParamsBuilder::class); + $this->objectManager = $this->createMock(ObjectManager::class); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + ImageFactory::class, + [ + 'objectManager' => $this->objectManager, + 'presentationConfig' => $configInterface, + 'viewAssetImageFactory' => $this->viewAssetImageFactory, + 'imageParamsBuilder' => $this->paramsBuilder + ] + ); + } + + /** + * @param array $data + * @dataProvider createDataProvider + */ + public function testCreate($data, $expected) + { + $product = $this->createMock(Product::class); + $product->method('getName')->willReturn($data['product']['name']); + $product->method('getData')->willReturnOnConsecutiveCalls( + $data['product']['image_type'], + $data['product']['image_type_label'] + ); + $imageBlock = $this->createMock(Image::class); + $this->viewConfig->method('getMediaAttributes')->willReturn($data['viewImageConfig']); + $this->viewConfig->method('getVarValue')->willReturn($data['frame']); + $this->viewAssetImageFactory->method('create')->willReturn( + $viewAssetImage = $this->createMock(ViewAssetImage::class) + ); + $this->paramsBuilder->method('build')->willReturn($data['imageParamsBuilder']); + $viewAssetImage->method('getUrl')->willReturn($data['url']); + + $this->objectManager->expects(self::once()) + ->method('create') + ->with(Image::class, $expected) + ->willReturn($imageBlock); + $actual = $this->model->create($product, 'image_id', $data['custom_attributes']); + self::assertInstanceOf(Image::class, $actual); + } + + /** + * @return array + */ + public function createDataProvider(): array + { + return [ + $this->getTestDataWithoutAttributes(), + $this->getTestDataWithAttributes(), + ]; + } + + /** + * @return array + */ + private function getTestDataWithoutAttributes(): array + { + return [ + 'data' => [ + 'viewImageConfig' => [ + 'width' => 100, + 'height' => 100, + 'constrain_only' => false, + 'aspect_ratio' => false, + 'frame' => false, + 'transparency' => false, + 'background' => '255,255,255', + 'type' => 'image_type' //thumbnail,small_image,image,swatch_image,swatch_thumb + ], + 'imageParamsBuilder' => [ + 'image_width' => 100, + 'image_height' => 100, + 'constrain_only' => false, + 'keep_aspect_ratio' => false, + 'keep_frame' => false, + 'keep_transparency' => false, + 'background' => '255,255,255', + 'image_type' => 'image_type', //thumbnail,small_image,image,swatch_image,swatch_thumb + 'quality' => 80, // <=== + 'angle' => null // <=== + ], + 'product' => [ + 'image_type_label' => 'test_image_label', + 'name' => 'test_product_name', + 'image_type' => 'test_image_path' + ], + 'url' => 'test_url_1', + 'frame' => 'test_frame', + 'custom_attributes' => [], + ], + 'expected' => [ + 'data' => [ + 'template' => 'Magento_Catalog::product/image_with_borders.phtml', + 'image_url' => 'test_url_1', + 'width' => 100, + 'height' => 100, + 'label' => 'test_image_label', + 'ratio' => 1, + 'custom_attributes' => '', + 'product_id' => null + ], + ], + ]; + } + + /** + * @return array + */ + private function getTestDataWithAttributes(): array + { + return [ + 'data' => [ + 'viewImageConfig' => [ + 'width' => 100, + 'height' => 50, // <=== + 'constrain_only' => false, + 'aspect_ratio' => false, + 'frame' => true, // <=== + 'transparency' => false, + 'background' => '255,255,255', + 'type' => 'image_type' //thumbnail,small_image,image,swatch_image,swatch_thumb + ], + 'imageParamsBuilder' => [ + 'image_width' => 100, + 'image_height' => 50, + 'constrain_only' => false, + 'keep_aspect_ratio' => false, + 'keep_frame' => true, + 'keep_transparency' => false, + 'background' => '255,255,255', + 'image_type' => 'image_type', //thumbnail,small_image,image,swatch_image,swatch_thumb + 'quality' => 80, + 'angle' => null + ], + 'product' => [ + 'image_type_label' => null, // <== + 'name' => 'test_product_name', + 'image_type' => 'test_image_path' + ], + 'url' => 'test_url_2', + 'frame' => 'test_frame', + 'custom_attributes' => [ + 'name_1' => 'value_1', + 'name_2' => 'value_2', + ], + ], + 'expected' => [ + 'data' => [ + 'template' => 'Magento_Catalog::product/image_with_borders.phtml', + 'image_url' => 'test_url_2', + 'width' => 100, + 'height' => 50, + 'label' => 'test_product_name', + 'ratio' => 0.5, // <== + 'custom_attributes' => 'name_1="value_1" name_2="value_2"', + 'product_id' => null + ], + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php index b10b9061a9b4..c9b7dc50beb9 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php @@ -5,23 +5,38 @@ */ namespace Magento\Catalog\Test\Unit\Block\Product\View; +use Magento\Catalog\Block\Product\Context; +use Magento\Catalog\Block\Product\ImageBuilder; +use Magento\Catalog\Block\Product\View\Gallery; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Framework\Data\Collection; +use Magento\Framework\DataObject; +use Magento\Framework\Json\EncoderInterface; +use Magento\Framework\Registry; +use Magento\Framework\Stdlib\ArrayUtils; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\Store; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GalleryTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Catalog\Block\Product\View\Gallery + * @var Gallery */ protected $model; /** - * @var \Magento\Catalog\Block\Product\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|\PHPUnit_Framework_MockObject_MockObject */ protected $context; /** - * @var \Magento\Framework\Stdlib\ArrayUtils|\PHPUnit_Framework_MockObject_MockObject + * @var ArrayUtils|\PHPUnit_Framework_MockObject_MockObject */ protected $arrayUtils; @@ -31,174 +46,94 @@ class GalleryTest extends \PHPUnit\Framework\TestCase protected $imageHelper; /** - * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + * @var Registry|\PHPUnit_Framework_MockObject_MockObject */ protected $registry; /** - * @var \Magento\Framework\Json\EncoderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var EncoderInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $jsonEncoderMock; /** - * @var \Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ImagesConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $imagesConfigFactoryMock; /** - * @var \Magento\Framework\Data\Collection|\PHPUnit_Framework_MockObject_MockObject + * @var Collection|\PHPUnit_Framework_MockObject_MockObject */ protected $galleryImagesConfigMock; + /** @var UrlBuilder|\PHPUnit_Framework_MockObject_MockObject */ + private $urlBuilder; + protected function setUp() { - $this->mockContext(); - - $this->arrayUtils = $this->getMockBuilder(\Magento\Framework\Stdlib\ArrayUtils::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->jsonEncoderMock = $this->getMockBuilder(\Magento\Framework\Json\EncoderInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->imagesConfigFactoryMock = $this->getImagesConfigFactory(); - - $this->model = new \Magento\Catalog\Block\Product\View\Gallery( - $this->context, - $this->arrayUtils, - $this->jsonEncoderMock, - [], - $this->imagesConfigFactoryMock + $this->registry = $this->createMock(Registry::class); + $this->context = $this->createConfiguredMock( + Context::class, + ['getRegistry' => $this->registry] ); - } - protected function mockContext() - { - $this->context = $this->getMockBuilder(\Magento\Catalog\Block\Product\Context::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->imageHelper = $this->getMockBuilder(\Magento\Catalog\Helper\Image::class) - ->disableOriginalConstructor() - ->getMock(); - $this->context->expects($this->any()) - ->method('getImageHelper') - ->willReturn($this->imageHelper); - - $this->registry = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->getMock(); - $this->context->expects($this->any()) - ->method('getRegistry') - ->willReturn($this->registry); + $this->arrayUtils = $this->createMock(ArrayUtils::class); + $this->jsonEncoderMock = $this->createMock(EncoderInterface::class); + $this->imagesConfigFactoryMock = $this->getImagesConfigFactory(); + $this->urlBuilder = $this->createMock(UrlBuilder::class); + + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject(Gallery::class, [ + 'context' => $this->context, + 'arrayUtils' => $this->arrayUtils, + 'jsonEncoder' => $this->jsonEncoderMock, + 'urlBuilder' => $this->urlBuilder, + 'imagesConfigFactory' => $this->imagesConfigFactoryMock + ]); } public function testGetGalleryImages() { - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $productTypeMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Type\AbstractType::class) - ->disableOriginalConstructor() - ->getMock(); - $productTypeMock->expects($this->once()) + $productMock = $this->createMock(Product::class); + $productTypeMock = $this->createMock(AbstractType::class); + $productTypeMock->expects(static::once()) ->method('getStoreFilter') ->with($productMock) - ->willReturn($storeMock); + ->willReturn($this->createMock(Store::class)); - $productMock->expects($this->once()) - ->method('getTypeInstance') - ->willReturn($productTypeMock); - $productMock->expects($this->once()) - ->method('getMediaGalleryImages') - ->willReturn($this->getImagesCollection()); + $imagesCollection = $this->createConfiguredMock( + Collection::class, + ['getIterator' => new \ArrayIterator([new DataObject(['file' => 'test_file'])])] + ); - $this->registry->expects($this->once()) + $productMock->method('getTypeInstance')->willReturn($productTypeMock); + $productMock->method('getMediaGalleryImages')->willReturn($imagesCollection); + $this->registry->expects(static::once()) ->method('registry') ->with('product') ->willReturn($productMock); - - $this->galleryImagesConfigMock->expects($this->exactly(1)) + $this->galleryImagesConfigMock->expects(static::exactly(1)) ->method('getItems') ->willReturn($this->getGalleryImagesConfigItems()); - $this->imageHelper->expects($this->exactly(3)) - ->method('init') - ->willReturnMap([ - [$productMock, 'product_page_image_small', [], $this->imageHelper], - [$productMock, 'product_page_image_medium_no_frame', [], $this->imageHelper], - [$productMock, 'product_page_image_large_no_frame', [], $this->imageHelper], - ]) - ->willReturnSelf(); - $this->imageHelper->expects($this->exactly(3)) - ->method('setImageFile') - ->with('test_file') - ->willReturnSelf(); - $this->imageHelper->expects($this->at(0)) - ->method('getUrl') - ->willReturn('product_page_image_small_url'); - $this->imageHelper->expects($this->at(1)) - ->method('getUrl') - ->willReturn('product_page_image_medium_url'); - $this->imageHelper->expects($this->at(2)) - ->method('getUrl') - ->willReturn('product_page_image_large_url'); - $images = $this->model->getGalleryImages(); - $this->assertInstanceOf(\Magento\Framework\Data\Collection::class, $images); - } - - /** - * @return \Magento\Framework\Data\Collection - */ - private function getImagesCollection() - { - $collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - - $items = [ - new \Magento\Framework\DataObject([ - 'file' => 'test_file' - ]), - ]; - - $collectionMock->expects($this->any()) - ->method('getIterator') - ->willReturn(new \ArrayIterator($items)); - - return $collectionMock; + static::assertInstanceOf(Collection::class, $images); } /** * getImagesConfigFactory * - * @return \Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface + * @return ImagesConfigFactoryInterface */ private function getImagesConfigFactory() { - $this->galleryImagesConfigMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->galleryImagesConfigMock->expects($this->any()) - ->method('getIterator') - ->willReturn(new \ArrayIterator($this->getGalleryImagesConfigItems())); - - $galleryImagesConfigFactoryMock = $this - ->getMockBuilder(\Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $galleryImagesConfigFactoryMock->expects($this->any()) - ->method('create') - ->willReturn($this->galleryImagesConfigMock); + $this->galleryImagesConfigMock = $this->createConfiguredMock( + Collection::class, + ['getIterator' => new \ArrayIterator($this->getGalleryImagesConfigItems())] + ); + $galleryImagesConfigFactoryMock = $this->createConfiguredMock( + ImagesConfigFactoryInterface::class, + ['create' => $this->galleryImagesConfigMock] + ); return $galleryImagesConfigFactoryMock; } @@ -211,17 +146,17 @@ private function getImagesConfigFactory() private function getGalleryImagesConfigItems() { return [ - new \Magento\Framework\DataObject([ + new DataObject([ 'image_id' => 'product_page_image_small', 'data_object_key' => 'small_image_url', 'json_object_key' => 'thumb' ]), - new \Magento\Framework\DataObject([ + new DataObject([ 'image_id' => 'product_page_image_medium', 'data_object_key' => 'medium_image_url', 'json_object_key' => 'img' ]), - new \Magento\Framework\DataObject([ + new DataObject([ 'image_id' => 'product_page_image_large', 'data_object_key' => 'large_image_url', 'json_object_key' => 'full' diff --git a/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php b/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php deleted file mode 100644 index 457ba7b94529..000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Console/Command/ImagesResizeCommandTest.php +++ /dev/null @@ -1,211 +0,0 @@ -appState = $this->getMockBuilder(\Magento\Framework\App\State::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->productRepository = $this->getMockBuilder(\Magento\Catalog\Api\ProductRepositoryInterface::class) - ->getMockForAbstractClass(); - - $this->prepareProductCollection(); - $this->prepareImageCache(); - - $this->command = new ImagesResizeCommand( - $this->appState, - $this->productCollectionFactory, - $this->productRepository, - $this->imageCacheFactory - ); - } - - public function testExecuteNoProducts() - { - $this->appState->expects($this->once()) - ->method('setAreaCode') - ->with(Area::AREA_GLOBAL) - ->willReturnSelf(); - - $this->productCollection->expects($this->once()) - ->method('getAllIds') - ->willReturn([]); - - $commandTester = new CommandTester($this->command); - $commandTester->execute([]); - - $this->assertContains( - 'No product images to resize', - $commandTester->getDisplay() - ); - } - - public function testExecute() - { - $productsIds = [1, 2]; - - $this->appState->expects($this->once()) - ->method('setAreaCode') - ->with(Area::AREA_GLOBAL) - ->willReturnSelf(); - - $this->productCollection->expects($this->once()) - ->method('getAllIds') - ->willReturn($productsIds); - - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->productRepository->expects($this->at(0)) - ->method('getById') - ->with($productsIds[0]) - ->willReturn($productMock); - $this->productRepository->expects($this->at(1)) - ->method('getById') - ->with($productsIds[1]) - ->willThrowException(new NoSuchEntityException()); - - $this->imageCache->expects($this->exactly(count($productsIds) - 1)) - ->method('generate') - ->with($productMock) - ->willReturnSelf(); - - $commandTester = new CommandTester($this->command); - $commandTester->execute([]); - - $this->assertContains( - 'Product images resized successfully', - $commandTester->getDisplay() - ); - } - - public function testExecuteWithException() - { - $productsIds = [1]; - $exceptionMessage = 'Test exception text'; - - $this->appState->expects($this->once()) - ->method('setAreaCode') - ->with(Area::AREA_GLOBAL) - ->willReturnSelf(); - - $this->productCollection->expects($this->once()) - ->method('getAllIds') - ->willReturn($productsIds); - - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->productRepository->expects($this->exactly(count($productsIds))) - ->method('getById') - ->with($productsIds[0]) - ->willReturn($productMock); - - $this->imageCache->expects($this->once()) - ->method('generate') - ->with($productMock) - ->willThrowException(new \Exception($exceptionMessage)); - - $commandTester = new CommandTester($this->command); - $commandTester->execute([]); - - $this->assertContains( - $exceptionMessage, - $commandTester->getDisplay() - ); - } - - protected function prepareProductCollection() - { - $this->productCollectionFactory = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class - ) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - - $this->productCollection = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Product\Collection::class - ) - ->disableOriginalConstructor() - ->getMock(); - - $this->productCollectionFactory->expects($this->any()) - ->method('create') - ->willReturn($this->productCollection); - } - - protected function prepareImageCache() - { - $this->imageCacheFactory = $this->getMockBuilder(\Magento\Catalog\Model\Product\Image\CacheFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - - $this->imageCache = $this->getMockBuilder(\Magento\Catalog\Model\Product\Image\Cache::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->imageCacheFactory->expects($this->any()) - ->method('create') - ->willReturn($this->imageCache); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/ImageTest.php index 6f4d30de1468..2759371dc96e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Helper/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Helper/ImageTest.php @@ -5,10 +5,12 @@ */ namespace Magento\Catalog\Test\Unit\Helper; +use Magento\Catalog\Helper\Image; + class ImageTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Catalog\Helper\Image + * @var Image */ protected $helper; @@ -63,7 +65,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->helper = new \Magento\Catalog\Helper\Image( + $this->helper = new Image( $this->context, $this->imageFactory, $this->assetRepository, @@ -118,7 +120,7 @@ public function testInit($data) $this->prepareWatermarkProperties($data); $this->assertInstanceOf( - \Magento\Catalog\Helper\Image::class, + Image::class, $this->helper->init($productMock, $imageId, $attributes) ); } @@ -160,7 +162,7 @@ protected function prepareAttributes($data, $imageId) ->getMock(); $configViewMock->expects($this->once()) ->method('getMediaAttributes') - ->with('Magento_Catalog', 'images', $imageId) + ->with('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId) ->willReturn($data); $this->viewConfig->expects($this->once()) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php index 627aa1848506..430db7070135 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php @@ -5,10 +5,12 @@ */ namespace Magento\Catalog\Test\Unit\Model\Product; +use Magento\Catalog\Model\Product\Image\ParamsBuilder; +use Magento\Catalog\Model\View\Asset\Image\ContextFactory; use Magento\Catalog\Model\View\Asset\ImageFactory; use Magento\Catalog\Model\View\Asset\PlaceholderFactory; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -81,6 +83,11 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ private $cacheManager; + /** + * @var ParamsBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $paramsBuilder; + protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -141,6 +148,9 @@ function ($value) { return json_decode($value, true); } ); + $this->paramsBuilder = $this->getMockBuilder(ParamsBuilder::class) + ->disableOriginalConstructor() + ->getMock(); $this->image = $objectManager->getObject( \Magento\Catalog\Model\Product\Image::class, @@ -153,15 +163,14 @@ function ($value) { 'imageFactory' => $this->factory, 'viewAssetImageFactory' => $this->viewAssetImageFactory, 'viewAssetPlaceholderFactory' => $this->viewAssetPlaceholderFactory, - 'serializer' => $this->serializer + 'serializer' => $this->serializer, + 'paramsBuilder' => $this->paramsBuilder ] ); - // Settings for backward compatible property - $objectManagerHelper = new ObjectManagerHelper($this); $this->imageAsset = $this->getMockBuilder(\Magento\Framework\View\Asset\LocalInterface::class) ->getMockForAbstractClass(); - $objectManagerHelper->setBackwardCompatibleProperty( + $objectManager->setBackwardCompatibleProperty( $this->image, 'imageAsset', $this->imageAsset @@ -213,6 +222,21 @@ public function testSetSize() public function testSetGetBaseFile() { + $miscParams = [ + 'image_type' => null, + 'image_height' => null, + 'image_width' => null, + 'keep_aspect_ratio' => 'proportional', + 'keep_frame' => 'frame', + 'keep_transparency' => 'transparency', + 'constrain_only' => 'doconstrainonly', + 'background' => 'ffffff', + 'angle' => null, + 'quality' => 80, + ]; + $this->paramsBuilder->expects(self::once()) + ->method('build') + ->willReturn($miscParams); $this->mediaDirectory->expects($this->any())->method('isFile')->will($this->returnValue(true)); $this->mediaDirectory->expects($this->any())->method('isExist')->will($this->returnValue(true)); $absolutePath = dirname(dirname(__DIR__)) . '/_files/catalog/product/somefile.png'; @@ -222,18 +246,7 @@ public function testSetGetBaseFile() ->method('create') ->with( [ - 'miscParams' => [ - 'image_type' => null, - 'image_height' => null, - 'image_width' => null, - 'keep_aspect_ratio' => 'proportional', - 'keep_frame' => 'frame', - 'keep_transparency' => 'transparency', - 'constrain_only' => 'doconstrainonly', - 'background' => 'ffffff', - 'angle' => null, - 'quality' => 80, - ], + 'miscParams' => $miscParams, 'filePath' => '/somefile.png', ] ) @@ -243,6 +256,10 @@ public function testSetGetBaseFile() $this->imageAsset->expects($this->any())->method('getSourceFile')->willReturn('catalog/product/somefile.png'); $this->image->setBaseFile('/somefile.png'); $this->assertEquals('catalog/product/somefile.png', $this->image->getBaseFile()); + $this->assertEquals( + null, + $this->image->getNewFile() + ); } public function testSetBaseNoSelectionFile() diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 96daf61b2b70..809ec8b9b7a2 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -870,13 +870,10 @@ public function testGetQty() */ public function testSave() { - $this->imageCache->expects($this->once()) - ->method('generate') - ->with($this->model); - $this->imageCacheFactory->expects($this->once()) - ->method('create') - ->willReturn($this->imageCache); - + $collection = $this->createMock(\Magento\Framework\Data\Collection::class); + $collection->method('count')->willReturn(1); + $collection->method('getIterator')->willReturn(new \ArrayObject([])); + $this->collectionFactoryMock->method('create')->willReturn($collection); $this->model->setIsDuplicate(false); $this->configureSaveTest(); $this->model->beforeSave(); @@ -902,13 +899,10 @@ public function testSaveIfAreaEmulated() */ public function testSaveAndDuplicate() { - $this->imageCache->expects($this->once()) - ->method('generate') - ->with($this->model); - $this->imageCacheFactory->expects($this->once()) - ->method('create') - ->willReturn($this->imageCache); - + $collection = $this->createMock(\Magento\Framework\Data\Collection::class); + $collection->method('count')->willReturn(1); + $collection->method('getIterator')->willReturn(new \ArrayObject([])); + $this->collectionFactoryMock->method('create')->willReturn($collection); $this->model->setIsDuplicate(true); $this->configureSaveTest(); $this->model->beforeSave(); @@ -1218,7 +1212,7 @@ public function testGetMediaGalleryImagesMerging() 'media_type' => 'image', ], [ - 'value_id' => 1, + 'value_id' => 3, 'file' => 'imageFile.jpg', ], [ @@ -1245,33 +1239,31 @@ public function testGetMediaGalleryImagesMerging() 'path' => '/var/www/html/pub/smallImageFile.jpg', ]); - $directoryMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->filesystemMock->expects($this->once())->method('getDirectoryRead')->willReturn($directoryMock); + $directoryMock = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); + $directoryMock->method('getAbsolutePath')->willReturnOnConsecutiveCalls( + '/var/www/html/pub/imageFile.jpg', + '/var/www/html/pub/smallImageFile.jpg' + ); + $this->mediaConfig->method('getMediaUrl')->willReturnOnConsecutiveCalls( + 'http://magento.dev/pub/imageFile.jpg', + 'http://magento.dev/pub/smallImageFile.jpg' + ); + $this->filesystemMock->method('getDirectoryRead')->willReturn($directoryMock); $this->model->setData('media_gallery', $mediaEntries); - $imagesCollectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($imagesCollectionMock); - $imagesCollectionMock->expects($this->at(2)) - ->method('getItemById') - ->with(1) - ->willReturn($expectedImageDataObject); - $this->mediaConfig->expects($this->at(0)) - ->method('getMediaUrl') - ->willReturn('http://magento.dev/pub/imageFile.jpg'); - $directoryMock->expects($this->at(0)) - ->method('getAbsolutePath') - ->willReturn('/var/www/html/pub/imageFile.jpg'); - $this->mediaConfig->expects($this->at(2)) - ->method('getMediaUrl') - ->willReturn('http://magento.dev/pub/smallImageFile.jpg'); - $directoryMock->expects($this->at(1)) - ->method('getAbsolutePath') - ->willReturn('/var/www/html/pub/smallImageFile.jpg'); - $imagesCollectionMock->expects($this->at(1))->method('addItem')->with($expectedImageDataObject); - $imagesCollectionMock->expects($this->at(4))->method('addItem')->with($expectedSmallImageDataObject); + $imagesCollectionMock = $this->createMock(\Magento\Framework\Data\Collection::class); + $imagesCollectionMock->method('count')->willReturn(0); + $imagesCollectionMock->method('getItemById')->willReturnMap( + [ + [1, null], + [2, null], + [3, 'not_null_skeep_foreache'], + ] + ); + $imagesCollectionMock->expects(self::exactly(2))->method('addItem')->withConsecutive( + $expectedImageDataObject, + $expectedSmallImageDataObject + ); + $this->collectionFactoryMock->method('create')->willReturn($imagesCollectionMock); $this->model->getMediaGalleryImages(); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php index 517b5949ee8e..431d5736bb6d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php @@ -8,7 +8,9 @@ use Magento\Catalog\Model\Product\Media\ConfigInterface; use Magento\Catalog\Model\View\Asset\Image; use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Asset\ContextInterface; +use Magento\Framework\View\Asset\Repository; /** * Class ImageTest @@ -33,18 +35,43 @@ class ImageTest extends \PHPUnit\Framework\TestCase /** * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $imageContext; + protected $context; + + /** + * @var Repository|\PHPUnit_Framework_MockObject_MockObject + */ + private $assetRepo; + + private $objectManager; protected function setUp() { - $this->mediaConfig = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); - $this->encryptor = $this->getMockBuilder(EncryptorInterface::class)->getMockForAbstractClass(); - $this->imageContext = $this->getMockBuilder(ContextInterface::class)->getMockForAbstractClass(); - $this->model = new Image( - $this->mediaConfig, - $this->imageContext, - $this->encryptor, - '/somefile.png' + $this->mediaConfig = $this->createMock(ConfigInterface::class); + $this->encryptor = $this->createMock(EncryptorInterface::class); + $this->context = $this->createMock(ContextInterface::class); + $this->assetRepo = $this->createMock(Repository::class); + $this->objectManager = new ObjectManager($this); + $this->model = $this->objectManager->getObject( + Image::class, + [ + 'mediaConfig' => $this->mediaConfig, + 'imageContext' => $this->context, + 'encryptor' => $this->encryptor, + 'filePath' => '/somefile.png', + 'assetRepo' => $this->assetRepo, + 'miscParams' => [ + 'image_width' => 100, + 'image_height' => 50, + 'constrain_only' => false, + 'keep_aspect_ratio' => false, + 'keep_frame' => true, + 'keep_transparency' => false, + 'background' => '255,255,255', + 'image_type' => 'image', //thumbnail,small_image,image,swatch_image,swatch_thumb + 'quality' => 80, + 'angle' => null + ] + ] ); } @@ -80,43 +107,24 @@ public function testGetContext() */ public function testGetPath($filePath, $miscParams) { - $imageModel = new Image( - $this->mediaConfig, - $this->imageContext, - $this->encryptor, - $filePath, - $miscParams + $imageModel = $this->objectManager->getObject( + Image::class, + [ + 'mediaConfig' => $this->mediaConfig, + 'context' => $this->context, + 'encryptor' => $this->encryptor, + 'filePath' => $filePath, + 'assetRepo' => $this->assetRepo, + 'miscParams' => $miscParams + ] ); + $miscParams['background'] = isset($miscParams['background']) ? implode(',', $miscParams['background']) : ''; $absolutePath = '/var/www/html/magento2ce/pub/media/catalog/product'; $hashPath = md5(implode('_', $miscParams)); - $this->imageContext->expects($this->once())->method('getPath')->willReturn($absolutePath); - $this->encryptor->expects($this->once())->method('hash')->willReturn($hashPath); - $this->assertEquals( - $absolutePath . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $hashPath . $filePath, - $imageModel->getPath() - ); - } - - /** - * @param string $filePath - * @param array $miscParams - * @dataProvider getPathDataProvider - */ - public function testGetNotUnixPath($filePath, $miscParams) - { - $imageModel = new Image( - $this->mediaConfig, - $this->imageContext, - $this->encryptor, - $filePath, - $miscParams - ); - $absolutePath = 'C:\www\magento2ce\pub\media\catalog\product'; - $hashPath = md5(implode('_', $miscParams)); - $this->imageContext->expects($this->once())->method('getPath')->willReturn($absolutePath); - $this->encryptor->expects($this->once())->method('hash')->willReturn($hashPath); - $this->assertEquals( - $absolutePath . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $hashPath . $filePath, + $this->context->method('getPath')->willReturn($absolutePath); + $this->encryptor->method('hash')->willReturn($hashPath); + static::assertEquals( + $absolutePath . '/cache/'. $hashPath . $filePath, $imageModel->getPath() ); } @@ -128,19 +136,24 @@ public function testGetNotUnixPath($filePath, $miscParams) */ public function testGetUrl($filePath, $miscParams) { - $imageModel = new Image( - $this->mediaConfig, - $this->imageContext, - $this->encryptor, - $filePath, - $miscParams + $imageModel = $this->objectManager->getObject( + Image::class, + [ + 'mediaConfig' => $this->mediaConfig, + 'context' => $this->context, + 'encryptor' => $this->encryptor, + 'filePath' => $filePath, + 'assetRepo' => $this->assetRepo, + 'miscParams' => $miscParams + ] ); + $miscParams['background'] = isset($miscParams['background']) ? implode(',', $miscParams['background']) : ''; $absolutePath = 'http://localhost/pub/media/catalog/product'; $hashPath = md5(implode('_', $miscParams)); - $this->imageContext->expects($this->once())->method('getBaseUrl')->willReturn($absolutePath); - $this->encryptor->expects($this->once())->method('hash')->willReturn($hashPath); - $this->assertEquals( - $absolutePath . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $hashPath . $filePath, + $this->context->expects(static::once())->method('getBaseUrl')->willReturn($absolutePath); + $this->encryptor->expects(static::once())->method('hash')->willReturn($hashPath); + static::assertEquals( + $absolutePath . '/cache/' . $hashPath . $filePath, $imageModel->getUrl() ); } @@ -162,7 +175,7 @@ public function getPathDataProvider() 'keep_frame' => 'frame', 'keep_transparency' => 'transparency', 'constrain_only' => 'doconstrainonly', - 'background' => 'ffffff', + 'background' => [233,1,0], 'angle' => null, 'quality' => 80, ], diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 9b6a1842b143..44d051933909 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -34,7 +34,7 @@ "suggest": { "magento/module-cookie": "*", "magento/module-sales": "*", - "magento/module-catalog-sample-data": "Sample Data version:100.3.*" + "magento/module-catalog-sample-data": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 39803c7ecc03..e6dbb10e811b 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -36,10 +36,10 @@ - + - + @@ -83,7 +83,7 @@ Magento\Catalog\Model\Indexer\Product\Flat\System\Config\Mode Magento\Config\Model\Config\Source\Yesno - + Magento\Catalog\Model\Config\Source\ListSort diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 3569c0a27b83..1d92197e390a 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -56,6 +56,7 @@ tmp + media/catalog/product/cache/ catalog custom_options diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index f39a78d922f9..6efd2d1c1eaf 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -1639,7 +1639,7 @@ - + diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 84b416e14316..875c3fecf37c 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -363,6 +363,7 @@ Magento\Catalog\Pricing\Price\BasePrice Magento\Catalog\Pricing\Price\CustomOptionPrice Magento\Catalog\Pricing\Price\ConfiguredPrice + Magento\Catalog\Pricing\Price\ConfiguredRegularPrice @@ -552,16 +553,10 @@ - Magento\Catalog\Console\Command\ImagesResizeCommand Magento\Catalog\Console\Command\ProductAttributesCleanUp - - - Magento\Catalog\Api\ProductRepositoryInterface\Proxy - - diff --git a/app/code/Magento/Catalog/etc/events.xml b/app/code/Magento/Catalog/etc/events.xml index 63bd57489433..5bcdc8836906 100644 --- a/app/code/Magento/Catalog/etc/events.xml +++ b/app/code/Magento/Catalog/etc/events.xml @@ -60,4 +60,7 @@ + + + diff --git a/app/code/Magento/Catalog/view/base/templates/product/price/configured_price.phtml b/app/code/Magento/Catalog/view/base/templates/product/price/configured_price.phtml index 98b713be685d..c7c6c8298621 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/price/configured_price.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/price/configured_price.phtml @@ -6,19 +6,58 @@ ?> getZone() == 'item_view') ? true : false; +$idSuffix = $block->getIdSuffix() ? $block->getIdSuffix() : ''; /** @var \Magento\Catalog\Pricing\Price\ConfiguredPrice $configuredPrice */ $configuredPrice = $block->getPrice(); -$schema = ($block->getZone() == 'item_view') ? true : false; -$priceLabel = ($block->getPriceLabel() !== null) - ? $block->getPriceLabel() - : ''; +/** @var \Magento\Catalog\Pricing\Price\ConfiguredRegularPrice $configuredRegularPrice */ +$configuredRegularPrice = $block->getPriceType( + \Magento\Catalog\Pricing\Price\ConfiguredPriceInterface::CONFIGURED_REGULAR_PRICE_CODE +); ?> -

- renderAmount($configuredPrice->getAmount(), [ - 'display_label' => $priceLabel, - 'price_id' => $block->getPriceId('product-price-'), - 'price_type' => 'finalPrice', - 'include_container' => true, - 'schema' => $schema - ]); ?> -

+getAmount()->getValue() < $configuredRegularPrice->getAmount()->getValue()) : ?> +

+ + renderAmount( + $configuredPrice->getAmount(), + [ + 'display_label' => $block->escapeHtml(__('Special Price')), + 'price_id' => $block->escapeHtml($block->getPriceId('product-price-' . $idSuffix)), + 'price_type' => 'finalPrice', + 'include_container' => true, + 'schema' => $schema, + ] + ); ?> + + + renderAmount( + $configuredRegularPrice->getAmount(), + [ + 'display_label' => $block->escapeHtml(__('Regular Price')), + 'price_id' => $block->escapeHtml($block->getPriceId('old-price-' . $idSuffix)), + 'price_type' => 'oldPrice', + 'include_container' => true, + 'skip_adjustments' => true, + ] + ); ?> + +

+ + getPriceLabel() !== null) + ? $block->getPriceLabel() + : ''; + ?> +

+ renderAmount( + $configuredPrice->getAmount(), + [ + 'display_label' => $block->escapeHtml($priceLabel), + 'price_id' => $block->escapeHtml($block->getPriceId('product-price-' . $idSuffix)), + 'price_type' => 'finalPrice', + 'include_container' => true, + 'schema' => $schema, + ] + ); ?> +

+ diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml index 6133d55d676c..c7abb0525b30 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/gallery.phtml @@ -25,10 +25,7 @@

helper('Magento\Catalog\Helper\Image') - ->init($block->getProduct(), 'product_page_image_large') - ->setImageFile($block->getImageFile()) - ->getUrl(); + $imageUrl = $block->getImageUrl(); ?> width="" alt="escapeHtml($block->getCurrentImage()->getLabel()) ?>" title="escapeHtml($block->getCurrentImage()->getLabel()) ?>" id="product-gallery-image" class="image" data-mage-init='{"catalogGallery":{}}'/>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 0352f7f27663..74a0b2d7cf1a 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -13,7 +13,7 @@ getCustomAttributes() ?> src="getImageUrl() ?>" - width="getResizedImageWidth() ?>" - height="getResizedImageHeight() ?>" + max-width="getWidth() ?>" + max-height="getHeight() ?>" alt="stripTags($block->getLabel(), null, true) ?>"/> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml index cad2b3aaa013..f7799b30436b 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml @@ -27,12 +27,12 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); getMode() == 'grid') { $viewMode = 'grid'; - $image = 'category_page_grid'; + $imageDisplayArea = 'category_page_grid'; $showDescription = false; $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW; } else { $viewMode = 'list'; - $image = 'category_page_list'; + $imageDisplayArea = 'category_page_list'; $showDescription = true; $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW; } @@ -48,7 +48,7 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output');
  • getImage($_product, $image); + $productImage = $block->getImage($_product, $imageDisplayArea); if ($pos != null) { $position = ' style="left:' . $productImage->getWidth() . 'px;' . 'top:' . $productImage->getHeight() . 'px;"'; diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index bb93de448c4b..5b09765d9ae5 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -17,7 +17,7 @@ }, "suggest": { "magento/module-import-export": "*", - "magento/module-catalog-rule-sample-data": "Sample Data version:100.3.*" + "magento/module-catalog-rule-sample-data": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php b/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php index 57ca4b7b2e60..06be39d0f351 100644 --- a/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php +++ b/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php @@ -611,9 +611,6 @@ public function getActions(AbstractItem $item) */ public function getImage($product, $imageId, $attributes = []) { - return $this->imageBuilder->setProduct($product) - ->setImageId($imageId) - ->setAttributes($attributes) - ->create(); + return $this->imageBuilder->create($product, $imageId, $attributes); } } diff --git a/app/code/Magento/Checkout/Block/Cart/Sidebar.php b/app/code/Magento/Checkout/Block/Cart/Sidebar.php index 5c237eecf0a9..92ba6bf2bbbb 100644 --- a/app/code/Magento/Checkout/Block/Cart/Sidebar.php +++ b/app/code/Magento/Checkout/Block/Cart/Sidebar.php @@ -100,9 +100,7 @@ public function getSerializedConfig() */ public function getImageHtmlTemplate() { - return $this->imageHelper->getFrame() - ? 'Magento_Catalog/product/image' - : 'Magento_Catalog/product/image_with_borders'; + return 'Magento_Catalog/product/image_with_borders'; } /** diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php index d963fa2d76e6..9c9c5fd33bd0 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Checkout\Test\Unit\Block\Cart\Item; +use Magento\Catalog\Block\Product\Image; +use Magento\Catalog\Model\Product; use Magento\Checkout\Block\Cart\Item\Renderer; use Magento\Quote\Model\Quote\Item; @@ -64,13 +66,13 @@ public function testGetProductForThumbnail() /** * Initialize product. * - * @return \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject + * @return Product|\PHPUnit_Framework_MockObject_MockObject */ protected function _initProduct() { - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */ + /** @var Product|\PHPUnit_Framework_MockObject_MockObject $product */ $product = $this->createPartialMock( - \Magento\Catalog\Model\Product::class, + Product::class, ['getName', '__wakeup', 'getIdentities'] ); $product->expects($this->any())->method('getName')->will($this->returnValue('Parent Product')); @@ -106,7 +108,7 @@ public function testGetIdentitiesFromEmptyItem() public function testGetProductPriceHtml() { $priceHtml = 'some price html'; - $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $product = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->getMock(); @@ -193,34 +195,17 @@ public function testGetImage() { $imageId = 'test_image_id'; $attributes = []; + $product = $this->createMock(Product::class); + $imageMock = $this->createMock(Image::class); - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $imageMock = $this->getMockBuilder(\Magento\Catalog\Block\Product\Image::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->imageBuilder->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $this->imageBuilder->expects($this->once()) - ->method('setImageId') - ->with($imageId) - ->willReturnSelf(); - $this->imageBuilder->expects($this->once()) - ->method('setAttributes') - ->with($attributes) - ->willReturnSelf(); - $this->imageBuilder->expects($this->once()) + $this->imageBuilder->expects(self::once()) ->method('create') + ->with($product, $imageId, $attributes) ->willReturn($imageMock); - $this->assertInstanceOf( - \Magento\Catalog\Block\Product\Image::class, - $this->_renderer->getImage($productMock, $imageId, $attributes) + static::assertInstanceOf( + Image::class, + $this->_renderer->getImage($product, $imageId, $attributes) ); } } diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php index 88751b899d7c..1c5224d007ec 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php @@ -163,7 +163,6 @@ public function testGetConfig() ->willReturnMap($valueMap); $this->storeManagerMock->expects($this->exactly(2))->method('getStore')->willReturn($storeMock); $storeMock->expects($this->once())->method('getBaseUrl')->willReturn($baseUrl); - $this->imageHelper->expects($this->once())->method('getFrame')->willReturn(false); $this->scopeConfigMock->expects($this->at(0)) ->method('getValue') diff --git a/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php b/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php index ed9ecc642e16..4a35a58a41ff 100644 --- a/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php +++ b/app/code/Magento/CheckoutAgreements/Block/Adminhtml/Agreement/Grid.php @@ -5,27 +5,42 @@ */ namespace Magento\CheckoutAgreements\Block\Adminhtml\Agreement; +use Magento\Framework\App\ObjectManager; +use Magento\CheckoutAgreements\Model\ResourceModel\Agreement\Grid\CollectionFactory as GridCollectionFactory; + class Grid extends \Magento\Backend\Block\Widget\Grid\Extended { /** * @var \Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory + * @deprecated */ protected $_collectionFactory; + /** + * @param GridCollectionFactory + */ + private $gridCollectionFactory; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Helper\Data $backendHelper * @param \Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory $collectionFactory * @param array $data + * @param GridCollectionFactory $gridColFactory * @codeCoverageIgnore */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Backend\Helper\Data $backendHelper, \Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory $collectionFactory, - array $data = [] + array $data = [], + GridCollectionFactory $gridColFactory = null ) { + $this->_collectionFactory = $collectionFactory; + $this->gridCollectionFactory = $gridColFactory + ? : ObjectManager::getInstance()->get(GridCollectionFactory::class); + parent::__construct($context, $backendHelper, $data); } @@ -47,7 +62,7 @@ protected function _construct() */ protected function _prepareCollection() { - $this->setCollection($this->_collectionFactory->create()); + $this->setCollection($this->gridCollectionFactory->create()); return parent::_prepareCollection(); } diff --git a/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement/Grid/Collection.php b/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement/Grid/Collection.php new file mode 100644 index 000000000000..70794d24a64e --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Model/ResourceModel/Agreement/Grid/Collection.php @@ -0,0 +1,78 @@ +isLoaded()) { + return $this; + } + + parent::load($printQuery, $logQuery); + + $this->addStoresToResult(); + + return $this; + } + + /** + * @return void + */ + private function addStoresToResult() + { + $stores = $this->getStoresForAgreements(); + + if (!empty($stores)) { + $storesByAgreementId = []; + + foreach ($stores as $storeData) { + $storesByAgreementId[$storeData['agreement_id']][] = $storeData['store_id']; + } + + foreach ($this as $item) { + $agreementId = $item->getData('agreement_id'); + + if (!isset($storesByAgreementId[$agreementId])) { + continue; + } + + $item->setData('stores', $storesByAgreementId[$agreementId]); + } + } + } + + /** + * @return array + */ + private function getStoresForAgreements() + { + $agreementId = $this->getColumnValues('agreement_id'); + + if (!empty($agreementId)) { + $select = $this->getConnection()->select()->from( + ['agreement_store' => 'checkout_agreement_store'] + )->where( + 'agreement_store.agreement_id IN (?)', + $agreementId + ); + + return $this->getConnection()->fetchAll($select); + } + + return []; + } +} diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index ab4dcefb7bb8..f051271c0505 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -18,7 +18,7 @@ "magento/module-widget": "*" }, "suggest": { - "magento/module-cms-sample-data": "Sample Data version:100.3.*" + "magento/module-cms-sample-data": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/ConfigurableProduct/Helper/Data.php b/app/code/Magento/ConfigurableProduct/Helper/Data.php index 1de82eaad319..674bd3703fa8 100644 --- a/app/code/Magento/ConfigurableProduct/Helper/Data.php +++ b/app/code/Magento/ConfigurableProduct/Helper/Data.php @@ -6,7 +6,11 @@ namespace Magento\ConfigurableProduct\Helper; -use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Helper\Image as ImageHelper; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product\Image; /** * Class Data @@ -17,50 +21,48 @@ class Data { /** - * Catalog Image Helper - * - * @var \Magento\Catalog\Helper\Image + * @var ImageHelper */ protected $imageHelper; /** - * @param \Magento\Catalog\Helper\Image $imageHelper + * @var UrlBuilder + */ + private $imageUrlBuilder; + + /** + * @param ImageHelper $imageHelper + * @param UrlBuilder $urlBuilder */ - public function __construct(\Magento\Catalog\Helper\Image $imageHelper) + public function __construct(ImageHelper $imageHelper, UrlBuilder $urlBuilder = null) { $this->imageHelper = $imageHelper; + $this->imageUrlBuilder = $urlBuilder ?? ObjectManager::getInstance()->get(UrlBuilder::class); } /** * Retrieve collection of gallery images * - * @param \Magento\Catalog\Api\Data\ProductInterface $product - * @return \Magento\Catalog\Model\Product\Image[]|null + * @param ProductInterface $product + * @return Image[]|null */ - public function getGalleryImages(\Magento\Catalog\Api\Data\ProductInterface $product) + public function getGalleryImages(ProductInterface $product) { $images = $product->getMediaGalleryImages(); if ($images instanceof \Magento\Framework\Data\Collection) { + /** @var $image Image */ foreach ($images as $image) { - /** @var $image \Magento\Catalog\Model\Product\Image */ - $image->setData( - 'small_image_url', - $this->imageHelper->init($product, 'product_page_image_small') - ->setImageFile($image->getFile()) - ->getUrl() - ); - $image->setData( - 'medium_image_url', - $this->imageHelper->init($product, 'product_page_image_medium_no_frame') - ->setImageFile($image->getFile()) - ->getUrl() - ); - $image->setData( - 'large_image_url', - $this->imageHelper->init($product, 'product_page_image_large_no_frame') - ->setImageFile($image->getFile()) - ->getUrl() - ); + $smallImageUrl = $this->imageUrlBuilder + ->getUrl($image->getFile(), 'product_page_image_small'); + $image->setData('small_image_url', $smallImageUrl); + + $mediumImageUrl = $this->imageUrlBuilder + ->getUrl($image->getFile(), 'product_page_image_medium'); + $image->setData('medium_image_url', $mediumImageUrl); + + $largeImageUrl = $this->imageUrlBuilder + ->getUrl($image->getFile(), 'product_page_image_large'); + $image->setData('large_image_url', $largeImageUrl); } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php index 92b7cace509b..cd9fb419981a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Helper/DataTest.php @@ -6,6 +6,9 @@ namespace Magento\ConfigurableProduct\Test\Unit\Helper; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + class DataTest extends \PHPUnit\Framework\TestCase { /** @@ -23,12 +26,27 @@ class DataTest extends \PHPUnit\Framework\TestCase */ protected $_productMock; + /** + * @var UrlBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $imageUrlBuilder; + protected function setUp() { + $objectManager = new ObjectManager($this); + $this->imageUrlBuilder = $this->getMockBuilder(UrlBuilder::class) + ->disableOriginalConstructor() + ->getMock(); $this->_imageHelperMock = $this->createMock(\Magento\Catalog\Helper\Image::class); $this->_productMock = $this->createMock(\Magento\Catalog\Model\Product::class); - $this->_model = new \Magento\ConfigurableProduct\Helper\Data($this->_imageHelperMock); + $this->_model = $objectManager->getObject( + \Magento\ConfigurableProduct\Helper\Data::class, + [ + '_imageHelper' => $this->_imageHelperMock + ] + ); + $objectManager->setBackwardCompatibleProperty($this->_model, 'imageUrlBuilder', $this->imageUrlBuilder); } public function testGetAllowAttributes() @@ -196,25 +214,38 @@ public function testGetGalleryImages() ->method('getMediaGalleryImages') ->willReturn($this->getImagesCollection()); - $this->_imageHelperMock->expects($this->exactly(3)) - ->method('init') - ->willReturnMap([ - [$productMock, 'product_page_image_small', [], $this->_imageHelperMock], - [$productMock, 'product_page_image_medium_no_frame', [], $this->_imageHelperMock], - [$productMock, 'product_page_image_large_no_frame', [], $this->_imageHelperMock], - ]) - ->willReturnSelf(); - $this->_imageHelperMock->expects($this->exactly(3)) + $this->imageUrlBuilder->expects($this->exactly(3)) + ->method('getUrl') + ->withConsecutive( + [ + self::identicalTo('test_file'), + self::identicalTo('product_page_image_small') + ], + [ + self::identicalTo('test_file'), + self::identicalTo('product_page_image_medium') + ], + [ + self::identicalTo('test_file'), + self::identicalTo('product_page_image_large') + ] + ) + ->will(self::onConsecutiveCalls( + 'testSmallImageUrl', + 'testMediumImageUrl', + 'testLargeImageUrl' + )); + $this->_imageHelperMock->expects(self::never()) ->method('setImageFile') ->with('test_file') ->willReturnSelf(); - $this->_imageHelperMock->expects($this->at(0)) + $this->_imageHelperMock->expects(self::never()) ->method('getUrl') ->willReturn('product_page_image_small_url'); - $this->_imageHelperMock->expects($this->at(1)) + $this->_imageHelperMock->expects(self::never()) ->method('getUrl') ->willReturn('product_page_image_medium_url'); - $this->_imageHelperMock->expects($this->at(2)) + $this->_imageHelperMock->expects(self::never()) ->method('getUrl') ->willReturn('product_page_image_large_url'); diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 9d292788269f..959c03698187 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -23,8 +23,8 @@ "magento/module-webapi": "*", "magento/module-sales": "*", "magento/module-product-video": "*", - "magento/module-configurable-sample-data": "Sample Data version:100.3.*", - "magento/module-product-links-sample-data": "Sample Data version:100.3.*" + "magento/module-configurable-sample-data": "*", + "magento/module-product-links-sample-data": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php b/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php index b98a456a511f..ed5e46d7a60f 100644 --- a/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php +++ b/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php @@ -290,8 +290,15 @@ protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, try { call_user_func_array($callback, [$schedule]); - } catch (\Exception $e) { + } catch (\Throwable $e) { $schedule->setStatus(Schedule::STATUS_ERROR); + if (!$e instanceof \Exception) { + $e = new \RuntimeException( + 'Error when running a cron job', + 0, + $e + ); + } throw $e; } diff --git a/app/code/Magento/Cron/Test/Unit/Model/CronJobException.php b/app/code/Magento/Cron/Test/Unit/Model/CronJobException.php index c50afa0e6f0d..6954fe49fdc4 100644 --- a/app/code/Magento/Cron/Test/Unit/Model/CronJobException.php +++ b/app/code/Magento/Cron/Test/Unit/Model/CronJobException.php @@ -12,8 +12,27 @@ class CronJobException { + /** + * @var \Throwable|null + */ + private $exception; + + /** + * @param \Throwable|null $exception + */ + public function __construct(\Throwable $exception = null) + { + $this->exception = $exception; + } + + /** + * @throws \Throwable + */ public function execute() { - throw new \Exception('Test exception'); + if (!$this->exception) { + $this->exception = new \Exception('Test exception'); + } + throw $this->exception; } } diff --git a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php index 0db6a598fb56..d8cb79af5213 100644 --- a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php +++ b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php @@ -468,6 +468,8 @@ public function testDispatchExceptionInCallback( */ public function dispatchExceptionInCallbackDataProvider() { + $throwable = new \TypeError(); + return [ 'non-callable callback' => [ 'Not_Existed_Class', @@ -483,6 +485,19 @@ public function dispatchExceptionInCallbackDataProvider() 2, new \Exception(__('Test exception')) ], + 'throwable in execution' => [ + 'CronJobException', + new \Magento\Cron\Test\Unit\Model\CronJobException( + $throwable + ), + 'Error when running a cron job', + 2, + new \RuntimeException( + 'Error when running a cron job', + 0, + $throwable + ), + ], ]; } diff --git a/app/code/Magento/Customer/Block/Account/Navigation.php b/app/code/Magento/Customer/Block/Account/Navigation.php index cb38d762769f..64ced9d592e1 100644 --- a/app/code/Magento/Customer/Block/Account/Navigation.php +++ b/app/code/Magento/Customer/Block/Account/Navigation.php @@ -46,6 +46,10 @@ public function getLinks() */ private function compare(SortLinkInterface $firstLink, SortLinkInterface $secondLink) { - return ($firstLink->getSortOrder() < $secondLink->getSortOrder()); + if ($firstLink->getSortOrder() == $secondLink->getSortOrder()) { + return 0; + } + + return ($firstLink->getSortOrder() < $secondLink->getSortOrder()) ? 1 : -1; } } diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php index 53c16a4b3709..3e326e087dd5 100644 --- a/app/code/Magento/Customer/Model/EmailNotification.php +++ b/app/code/Magento/Customer/Model/EmailNotification.php @@ -7,6 +7,8 @@ namespace Magento\Customer\Model; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Mail\Template\SenderResolverInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Customer\Helper\View as CustomerViewHelper; @@ -91,6 +93,11 @@ class EmailNotification implements EmailNotificationInterface */ private $scopeConfig; + /** + * @var SenderResolverInterface + */ + private $senderResolver; + /** * @param CustomerRegistry $customerRegistry * @param StoreManagerInterface $storeManager @@ -98,6 +105,7 @@ class EmailNotification implements EmailNotificationInterface * @param CustomerViewHelper $customerViewHelper * @param DataObjectProcessor $dataProcessor * @param ScopeConfigInterface $scopeConfig + * @param SenderResolverInterface|null $senderResolver */ public function __construct( CustomerRegistry $customerRegistry, @@ -105,7 +113,8 @@ public function __construct( TransportBuilder $transportBuilder, CustomerViewHelper $customerViewHelper, DataObjectProcessor $dataProcessor, - ScopeConfigInterface $scopeConfig + ScopeConfigInterface $scopeConfig, + SenderResolverInterface $senderResolver = null ) { $this->customerRegistry = $customerRegistry; $this->storeManager = $storeManager; @@ -113,6 +122,7 @@ public function __construct( $this->customerViewHelper = $customerViewHelper; $this->dataProcessor = $dataProcessor; $this->scopeConfig = $scopeConfig; + $this->senderResolver = $senderResolver ?: ObjectManager::getInstance()->get(SenderResolverInterface::class); } /** @@ -231,6 +241,7 @@ private function passwordReset(CustomerInterface $customer) * @param int|null $storeId * @param string $email * @return void + * @throws \Magento\Framework\Exception\MailException */ private function sendEmailTemplate( $customer, @@ -244,10 +255,17 @@ private function sendEmailTemplate( if ($email === null) { $email = $customer->getEmail(); } + + /** @var array $from */ + $from = $this->senderResolver->resolve( + $this->scopeConfig->getValue($sender, 'store', $storeId), + $storeId + ); + $transport = $this->transportBuilder->setTemplateIdentifier($templateId) ->setTemplateOptions(['area' => 'frontend', 'store' => $storeId]) ->setTemplateVars($templateParams) - ->setFrom($this->scopeConfig->getValue($sender, 'store', $storeId)) + ->setFrom($from) ->addTo($email, $this->customerViewHelper->getCustomerName($customer)) ->getTransport(); diff --git a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php index 0240b7ab29ab..61e58af78fcd 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/EmailNotificationTest.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Test\Unit\Model; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\EmailNotification; use Magento\Framework\App\Area; +use Magento\Framework\Mail\Template\SenderResolverInterface; use Magento\Store\Model\ScopeInterface; /** @@ -47,7 +50,7 @@ class EmailNotificationTest extends \PHPUnit\Framework\TestCase private $customerSecureMock; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ private $scopeConfigMock; @@ -61,6 +64,11 @@ class EmailNotificationTest extends \PHPUnit\Framework\TestCase */ private $model; + /** + * @var SenderResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $senderResolverMock; + public function setUp() { $this->customerRegistryMock = $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); @@ -88,17 +96,23 @@ public function setUp() $this->storeMock = $this->createMock(\Magento\Store\Model\Store::class); + $this->senderResolverMock = $this->getMockBuilder(SenderResolverInterface::class) + ->setMethods(['resolve']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectManager->getObject( EmailNotification::class, [ - 'customerRegistry' => $this->customerRegistryMock, + 'customerRegistry' => $this->customerRegistryMock, 'storeManager' => $this->storeManagerMock, 'transportBuilder' => $this->transportBuilderMock, 'customerViewHelper' => $this->customerViewHelperMock, 'dataProcessor' => $this->dataProcessorMock, - 'scopeConfig' => $this->scopeConfigMock + 'scopeConfig' => $this->scopeConfigMock, + 'senderResolver' => $this->senderResolverMock, ] ); } @@ -121,7 +135,10 @@ public function testCredentialsChanged($testNumber, $oldEmail, $newEmail, $isPas $customerName = 'Customer Name'; $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; + $senderValues = ['name' => $sender, 'email' => $sender]; + $expects = $this->once(); + $xmlPathTemplate = EmailNotification::XML_PATH_RESET_PASSWORD_TEMPLATE; switch ($testNumber) { case 1: $xmlPathTemplate = EmailNotification::XML_PATH_RESET_PASSWORD_TEMPLATE; @@ -137,7 +154,14 @@ public function testCredentialsChanged($testNumber, $oldEmail, $newEmail, $isPas break; } - $origCustomer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); + $this->senderResolverMock + ->expects($expects) + ->method('resolve') + ->with($sender, $customerStoreId) + ->willReturn($senderValues); + + /** @var \PHPUnit_Framework_MockObject_MockObject $origCustomer */ + $origCustomer = $this->createMock(CustomerInterface::class); $origCustomer->expects($this->any()) ->method('getStoreId') ->willReturn(0); @@ -175,7 +199,7 @@ public function testCredentialsChanged($testNumber, $oldEmail, $newEmail, $isPas $this->dataProcessorMock->expects(clone $expects) ->method('buildOutputDataArray') - ->with($origCustomer, \Magento\Customer\Api\Data\CustomerInterface::class) + ->with($origCustomer, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) @@ -192,6 +216,7 @@ public function testCredentialsChanged($testNumber, $oldEmail, $newEmail, $isPas ->with('name', $customerName) ->willReturnSelf(); + /** @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $savedCustomer */ $savedCustomer = clone $origCustomer; $origCustomer->expects($this->any()) @@ -234,7 +259,7 @@ public function testCredentialsChanged($testNumber, $oldEmail, $newEmail, $isPas ->willReturnSelf(); $this->transportBuilderMock->expects(clone $expects) ->method('setFrom') - ->with($sender) + ->with($senderValues) ->willReturnSelf(); $this->transportBuilderMock->expects(clone $expects) @@ -293,8 +318,16 @@ public function testPasswordReminder() $customerName = 'Customer Name'; $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; + $senderValues = ['name' => $sender, 'email' => $sender]; + + $this->senderResolverMock + ->expects($this->once()) + ->method('resolve') + ->with($sender, $customerStoreId) + ->willReturn($senderValues); - $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->createMock(CustomerInterface::class); $customer->expects($this->any()) ->method('getStoreId') ->willReturn($customerStoreId); @@ -325,7 +358,7 @@ public function testPasswordReminder() $this->dataProcessorMock->expects($this->once()) ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) + ->with($customer, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) @@ -351,34 +384,14 @@ public function testPasswordReminder() ->with(EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $customerStoreId) ->willReturn($sender); - $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); - - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => $customerStoreId]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateVars') - ->with(['customer' => $this->customerSecureMock, 'store' => $this->storeMock]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setFrom') - ->with($sender) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('addTo') - ->with($customerEmail, $customerName) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('getTransport') - ->willReturn($transport); - - $transport->expects($this->once()) - ->method('sendMessage'); + $this->mockDefaultTransportBuilder( + $templateIdentifier, + $customerStoreId, + $senderValues, + $customerEmail, + $customerName, + ['customer' => $this->customerSecureMock, 'store' => $this->storeMock] + ); $this->model->passwordReminder($customer); } @@ -395,8 +408,16 @@ public function testPasswordResetConfirmation() $customerName = 'Customer Name'; $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; + $senderValues = ['name' => $sender, 'email' => $sender]; + + $this->senderResolverMock + ->expects($this->once()) + ->method('resolve') + ->with($sender, $customerStoreId) + ->willReturn($senderValues); - $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->createMock(CustomerInterface::class); $customer->expects($this->any()) ->method('getStoreId') ->willReturn($customerStoreId); @@ -427,7 +448,7 @@ public function testPasswordResetConfirmation() $this->dataProcessorMock->expects($this->once()) ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) + ->with($customer, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) @@ -453,34 +474,14 @@ public function testPasswordResetConfirmation() ->with(EmailNotification::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $customerStoreId) ->willReturn($sender); - $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); - - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => $customerStoreId]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateVars') - ->with(['customer' => $this->customerSecureMock, 'store' => $this->storeMock]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setFrom') - ->with($sender) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('addTo') - ->with($customerEmail, $customerName) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('getTransport') - ->willReturn($transport); - - $transport->expects($this->once()) - ->method('sendMessage'); + $this->mockDefaultTransportBuilder( + $templateIdentifier, + $customerStoreId, + $senderValues, + $customerEmail, + $customerName, + ['customer' => $this->customerSecureMock, 'store' => $this->storeMock] + ); $this->model->passwordResetConfirmation($customer); } @@ -497,8 +498,16 @@ public function testNewAccount() $customerName = 'Customer Name'; $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; + $senderValues = ['name' => $sender, 'email' => $sender]; + + $this->senderResolverMock + ->expects($this->once()) + ->method('resolve') + ->with($sender, $customerStoreId) + ->willReturn($senderValues); - $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->createMock(CustomerInterface::class); $customer->expects($this->any()) ->method('getStoreId') ->willReturn($customerStoreId); @@ -525,7 +534,7 @@ public function testNewAccount() $this->dataProcessorMock->expects($this->once()) ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) + ->with($customer, CustomerInterface::class) ->willReturn($customerData); $this->customerViewHelperMock->expects($this->any()) @@ -551,6 +560,38 @@ public function testNewAccount() ->with(EmailNotification::XML_PATH_REGISTER_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $customerStoreId) ->willReturn($sender); + $this->mockDefaultTransportBuilder( + $templateIdentifier, + $customerStoreId, + $senderValues, + $customerEmail, + $customerName, + ['customer' => $this->customerSecureMock, 'back_url' => '', 'store' => $this->storeMock] + ); + + $this->model->newAccount($customer, EmailNotification::NEW_ACCOUNT_EMAIL_REGISTERED, '', $customerStoreId); + } + + /** + * Create default mock for $this->transportBuilderMock. + * + * @param string $templateIdentifier + * @param int $customerStoreId + * @param array $senderValues + * @param string $customerEmail + * @param string $customerName + * @param array $templateVars + * + * @return void + */ + private function mockDefaultTransportBuilder( + string $templateIdentifier, + int $customerStoreId, + array $senderValues, + string $customerEmail, + string $customerName, + array $templateVars = [] + ): void { $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); $this->transportBuilderMock->expects($this->once()) @@ -563,11 +604,11 @@ public function testNewAccount() ->willReturnSelf(); $this->transportBuilderMock->expects($this->once()) ->method('setTemplateVars') - ->with(['customer' => $this->customerSecureMock, 'back_url' => '', 'store' => $this->storeMock]) + ->with($templateVars) ->willReturnSelf(); $this->transportBuilderMock->expects($this->once()) ->method('setFrom') - ->with($sender) + ->with($senderValues) ->willReturnSelf(); $this->transportBuilderMock->expects($this->once()) ->method('addTo') @@ -579,7 +620,5 @@ public function testNewAccount() $transport->expects($this->once()) ->method('sendMessage'); - - $this->model->newAccount($customer, EmailNotification::NEW_ACCOUNT_EMAIL_REGISTERED, '', $customerStoreId); } } diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index 71b26fddbed7..b9a7aca73fe3 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -29,7 +29,7 @@ }, "suggest": { "magento/module-cookie": "*", - "magento/module-customer-sample-data": "Sample Data version:100.3.*" + "magento/module-customer-sample-data": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Customer/view/frontend/templates/account/dashboard/info.phtml b/app/code/Magento/Customer/view/frontend/templates/account/dashboard/info.phtml index 7881b7e857fd..ac8e1298b29b 100644 --- a/app/code/Magento/Customer/view/frontend/templates/account/dashboard/info.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/account/dashboard/info.phtml @@ -20,6 +20,7 @@ escapeHtml($block->getName()) ?>
    escapeHtml($block->getCustomer()->getEmail()) ?>

    + getChildHtml('customer.account.dashboard.info.extra'); ?>
    escapeHtml(__('Edit')) ?> diff --git a/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html b/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html index ad3d62f6c1c2..6b3a232cd3e3 100644 --- a/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html +++ b/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html @@ -54,10 +54,10 @@ id="login-form">