diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php index 8e4b59d396986..38216ef98d2dd 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php @@ -53,8 +53,10 @@ public function __construct( */ public function execute() { - $collection = $this->filter->getCollection($this->collectionFactory->create()); - $this->attributeHelper->setProductIds($collection->getAllIds()); + if ($this->getRequest()->getParam('filters')) { + $collection = $this->filter->getCollection($this->collectionFactory->create()); + $this->attributeHelper->setProductIds($collection->getAllIds()); + } if (!$this->_validateProducts()) { return $this->resultRedirectFactory->create()->setPath('catalog/product/', ['_current' => true]); diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php new file mode 100644 index 0000000000000..80661f48f5289 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php @@ -0,0 +1,175 @@ +attributeHelper = $this->getMockBuilder('Magento\Catalog\Helper\Product\Edit\Action\Attribute') + ->setMethods(['getProductIds', 'setProductIds']) + ->disableOriginalConstructor()->getMock(); + + $this->resultRedirectFactory = $this->getMockBuilder('Magento\Backend\Model\View\Result\RedirectFactory') + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->filter = $this->getMockBuilder('Magento\Ui\Component\MassAction\Filter') + ->setMethods(['getCollection'])->disableOriginalConstructor()->getMock(); + + $this->collectionFactory = $this->getMockBuilder( + 'Magento\Catalog\Model\ResourceModel\Product\CollectionFactory' + )->setMethods(['create'])->disableOriginalConstructor()->getMock(); + + $this->resultPage = $this->getMockBuilder('Magento\Framework\View\Result\Page') + ->setMethods(['getConfig'])->disableOriginalConstructor()->getMock(); + + $resultPageFactory = $this->getMockBuilder('Magento\Framework\View\Result\PageFactory') + ->setMethods(['create'])->disableOriginalConstructor()->getMock(); + $resultPageFactory->expects($this->any())->method('create')->willReturn($this->resultPage); + + $this->prepareContext(); + + $this->object = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + 'Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Edit', + [ + 'context' => $this->context, + 'attributeHelper' => $this->attributeHelper, + 'filter' => $this->filter, + 'resultPageFactory' => $resultPageFactory, + 'collectionFactory' => $this->collectionFactory + ] + ); + } + + private function prepareContext() + { + $this->request = $this->getMockBuilder('Magento\Framework\App\Request\Http') + ->setMethods(['getParam', 'getParams', 'setParams']) + ->disableOriginalConstructor()->getMock(); + + $objectManager = $this->getMock('Magento\Framework\ObjectManagerInterface'); + $product = $this->getMockBuilder('Magento\Catalog\Model\Product') + ->setMethods(['isProductsHasSku']) + ->disableOriginalConstructor()->getMock(); + $product->expects($this->any())->method('isProductsHasSku') + ->with([1, 2, 3]) + ->willReturn(true); + $objectManager->expects($this->any())->method('create') + ->with('Magento\Catalog\Model\Product') + ->willReturn($product); + $messageManager = $this->getMockBuilder('\Magento\Framework\Message\ManagerInterface') + ->setMethods([]) + ->disableOriginalConstructor()->getMock(); + $messageManager->expects($this->any())->method('addError')->willReturn(true); + $this->context = $this->getMockBuilder('Magento\Backend\App\Action\Context') + ->setMethods(['getRequest', 'getObjectManager', 'getMessageManager', 'getResultRedirectFactory']) + ->disableOriginalConstructor()->getMock(); + $this->context->expects($this->any())->method('getRequest')->willReturn($this->request); + $this->context->expects($this->any())->method('getObjectManager')->willReturn($objectManager); + $this->context->expects($this->any())->method('getMessageManager')->willReturn($messageManager); + $this->context->expects($this->any())->method('getResultRedirectFactory') + ->willReturn($this->resultRedirectFactory); + } + + public function testExecutePageRequested() + { + $this->request->expects($this->any())->method('getParam')->with('filters')->willReturn(['placeholder' => true]); + $this->request->expects($this->any())->method('getParams')->willReturn( + [ + 'namespace' => 'product_listing', + 'exclude' => true, + 'filters' => ['placeholder' => true] + ] + ); + + $this->attributeHelper->expects($this->any())->method('getProductIds')->willReturn([1, 2, 3]); + $this->attributeHelper->expects($this->any())->method('setProductIds')->with([1, 2, 3]); + + $collection = $this->getMockBuilder('Magento\Catalog\Model\ResourceModel\Product\Collection') + ->setMethods(['getAllIds']) + ->disableOriginalConstructor()->getMock(); + $collection->expects($this->any())->method('getAllIds')->willReturn([1, 2, 3]); + $this->filter->expects($this->any())->method('getCollection')->with($collection)->willReturn($collection); + $this->collectionFactory->expects($this->any())->method('create')->willReturn($collection); + + $title = $this->getMockBuilder('Magento\Framework\View\Page\Title') + ->setMethods(['prepend']) + ->disableOriginalConstructor()->getMock(); + $config = $this->getMockBuilder('Magento\Framework\View\Page\Config') + ->setMethods(['getTitle']) + ->disableOriginalConstructor()->getMock(); + $config->expects($this->any())->method('getTitle')->willReturn($title); + $this->resultPage->expects($this->any())->method('getConfig')->willReturn($config); + + $this->assertSame($this->resultPage, $this->object->execute()); + } + + public function testExecutePageReload() + { + $this->request->expects($this->any())->method('getParam')->with('filters')->willReturn(null); + $this->request->expects($this->any())->method('getParams')->willReturn([]); + + $this->attributeHelper->expects($this->any())->method('getProductIds')->willReturn([1, 2, 3]); + $this->attributeHelper->expects($this->any())->method('setProductIds')->with([1, 2, 3]); + + $title = $this->getMockBuilder('Magento\Framework\View\Page\Title') + ->setMethods(['prepend']) + ->disableOriginalConstructor()->getMock(); + $config = $this->getMockBuilder('Magento\Framework\View\Page\Config') + ->setMethods(['getTitle']) + ->disableOriginalConstructor()->getMock(); + $config->expects($this->any())->method('getTitle')->willReturn($title); + $this->resultPage->expects($this->any())->method('getConfig')->willReturn($config); + + $this->assertSame($this->resultPage, $this->object->execute()); + } + + public function testExecutePageDirectAccess() + { + $this->request->expects($this->any())->method('getParam')->with('filters')->willReturn(null); + $this->request->expects($this->any())->method('getParams')->willReturn([]); + $this->attributeHelper->expects($this->any())->method('getProductIds')->willReturn(null); + + $resultRedirect = $this->getMockBuilder('Magento\Backend\Model\View\Result\Redirect') + ->setMethods(['setPath']) + ->disableOriginalConstructor() + ->getMock(); + $resultRedirect->expects($this->any())->method('setPath') + ->with('catalog/product/', ['_current' => true]) + ->willReturnSelf(); + $this->resultRedirectFactory->expects($this->any()) + ->method('create') + ->willReturn($resultRedirect); + + $this->assertSame($resultRedirect, $this->object->execute()); + } +} diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 6f4c6d1f53a15..c2fe86a8ec4c4 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -16,6 +16,7 @@ use Magento\ImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; +use Magento\Catalog\Model\Product\Visibility; /** * Import entity product model @@ -2237,7 +2238,8 @@ public function validateRow(array $rowData, $rowNum) } // validate custom options $this->getOptionEntity()->validateRow($rowData, $rowNum); - if (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) { + + if ($this->isNeedToValidateUrlKey($rowData)) { $urlKey = $this->getUrlKey($rowData); $storeCodes = empty($rowData[self::COL_STORE_VIEW_CODE]) ? array_flip($this->storeResolver->getStoreCodeToId()) @@ -2259,6 +2261,18 @@ public function validateRow(array $rowData, $rowNum) return !$this->getErrorAggregator()->isRowInvalid($rowNum); } + /** + * @param array $rowData + * @return bool + */ + private function isNeedToValidateUrlKey($rowData) + { + return (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) + && (empty($rowData['visibility']) + || $rowData['visibility'] + !== (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]); + } + /** * Parse attributes names and values string to array. * diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php index 5892371a4ec29..f3c86bad49936 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php @@ -113,7 +113,7 @@ protected function generateProductUrls($websiteId, $originWebsiteId) $collection = $this->productFactory->create() ->getCollection() ->addCategoryIds() - ->addAttributeToSelect(['name', 'url_path', 'url_key']) + ->addAttributeToSelect(['name', 'url_path', 'url_key', 'visibility']) ->addWebsiteFilter($websiteIds); foreach ($collection as $product) { /** @var \Magento\Catalog\Model\Product $product */ diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php index df9af24075a48..860e5480988ca 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/View.php @@ -14,6 +14,9 @@ use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class View { /** @var UrlPersistInterface */ @@ -100,7 +103,7 @@ protected function generateProductUrls($websiteId, $originWebsiteId, $storeId) $collection = $this->productFactory->create() ->getCollection() ->addCategoryIds() - ->addAttributeToSelect(['name', 'url_path', 'url_key']) + ->addAttributeToSelect(['name', 'url_path', 'url_key', 'visibility']) ->addWebsiteFilter($websiteIds); foreach ($collection as $product) { $product->setStoreId($storeId); diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php index 6dc4ab0f95c6f..098ac4a88391a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php @@ -11,6 +11,7 @@ use Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator; use Magento\CatalogUrlRewrite\Service\V1\StoreViewService; use Magento\Store\Model\Store; +use Magento\Catalog\Model\Product\Visibility; class ProductUrlRewriteGenerator { @@ -75,6 +76,10 @@ public function __construct( */ public function generate(Product $product) { + if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) { + return []; + } + $this->product = $product; $storeId = $this->product->getStoreId(); diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index 49c91b1e891fe..0f2a00b7ebfc1 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -19,6 +19,7 @@ use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\Framework\Event\ObserverInterface; +use Magento\Catalog\Model\Product\Visibility; /** * Class AfterImportDataObserver @@ -93,6 +94,7 @@ class AfterImportDataObserver implements ObserverInterface 'url_key', 'url_path', 'name', + 'visibility', ]; /** @@ -221,6 +223,10 @@ protected function setStoreToProduct(\Magento\Catalog\Model\Product $product, ar */ protected function addProductToImport($product, $storeId) { + if ($product->getVisibility() == (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]) { + return $this; + } + if (!isset($this->products[$product->getId()])) { $this->products[$product->getId()] = []; } @@ -321,6 +327,9 @@ protected function categoriesUrlRewriteGenerate() foreach ($productsByStores as $storeId => $product) { foreach ($this->categoryCache[$productId] as $categoryId) { $category = $this->import->getCategoryProcessor()->getCategoryById($categoryId); + if ($category->getParentId() == Category::TREE_ROOT_ID) { + continue; + } $requestPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category); $urls[] = $this->urlRewriteFactory->create() ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE) diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php index 75a63ab0c9727..2b7bfe017455b 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php @@ -47,14 +47,15 @@ public function execute(\Magento\Framework\Event\Observer $observer) $product = $observer->getEvent()->getProduct(); $isChangedWebsites = $product->getIsChangedWebsites(); - if ($product->dataHasChangedFor('url_key') || $product->getIsChangedCategories() || $isChangedWebsites) { + if ($product->dataHasChangedFor('url_key') || $product->getIsChangedCategories() || $isChangedWebsites + || $product->dataHasChangedFor('visibility')) { if ($isChangedWebsites) { $this->urlPersist->deleteByData([ UrlRewrite::ENTITY_ID => $product->getId(), UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, ]); } - if ($product->getVisibility() != Visibility::VISIBILITY_NOT_VISIBLE) { + if (!in_array($product->getOrigData('visibility'), $product->getVisibleInSiteVisibilities())) { $this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index cd044d6e6fc87..b02ef76d95146 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -70,6 +70,7 @@ public function generateProductUrlRewrites(Category $category) $collection = $this->productCollectionFactory->create() ->setStoreId($storeId) ->addIdFilter($category->getAffectedProductIds()) + ->addAttributeToSelect('visibility') ->addAttributeToSelect('name') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); @@ -104,6 +105,7 @@ public function getCategoryProductsUrlRewrites(Category $category, $storeId, $sa /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ $productCollection = $category->getProductCollection() ->addAttributeToSelect('name') + ->addAttributeToSelect('visibility') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); $productUrls = []; diff --git a/app/code/Magento/Checkout/Helper/Data.php b/app/code/Magento/Checkout/Helper/Data.php index 2073ffe87d4d6..25d85dd4ee0cd 100644 --- a/app/code/Magento/Checkout/Helper/Data.php +++ b/app/code/Magento/Checkout/Helper/Data.php @@ -304,6 +304,8 @@ public function sendPaymentFailedEmail($checkout, $message, $checkoutType = 'one 'items' => nl2br($items), 'total' => $total, ] + )->setScopeId( + $checkout->getStoreId() )->setFrom( $this->scopeConfig->getValue( 'checkout/payment_failed/identity', diff --git a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php index 9128419543e1c..96d6ac51ca15a 100644 --- a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php @@ -166,6 +166,16 @@ public function testSendPaymentFailedEmail() $this->returnSelf() ); + $this->_transportBuilder->expects( + $this->once() + )->method( + 'setScopeId' + )->with( + 8 + )->will( + $this->returnSelf() + ); + $this->_transportBuilder->expects( $this->once() )->method( diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index ee1201fab1c3e..07adf7b25f4cc 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -920,6 +920,7 @@ protected function sendEmailTemplate($customer, $template, $sender, $templatePar $transport = $this->transportBuilder->setTemplateIdentifier($templateId) ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId]) ->setTemplateVars($templateParams) + ->setScopeId($storeId) ->setFrom($this->scopeConfig->getValue($sender, ScopeInterface::SCOPE_STORE, $storeId)) ->addTo($customer->getEmail(), $this->customerViewHelper->getCustomerName($customer)) ->getTransport(); diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 36e848729cd77..d46fc6dcdff20 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -828,6 +828,8 @@ protected function _sendEmailTemplate($template, $sender, $templateParams = [], ['area' => \Magento\Framework\App\Area::AREA_FRONTEND, 'store' => $storeId] )->setTemplateVars( $templateParams + )->setScopeId( + $storeId )->setFrom( $this->_scopeConfig->getValue($sender, \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $storeId) )->addTo( diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 43e6973d9fd5c..9163573d121c3 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -650,6 +650,10 @@ public function testCreateAccountWithoutPassword() $this->transportBuilder->expects($this->once()) ->method('setTemplateVars') ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('setScopeId') + ->with($defaultStoreId) + ->willReturnSelf(); $this->transportBuilder->expects($this->once()) ->method('setFrom') ->with($sender) @@ -799,6 +803,10 @@ function ($string) { $this->transportBuilder->expects($this->once()) ->method('setTemplateVars') ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('setScopeId') + ->with($defaultStoreId) + ->willReturnSelf(); $this->transportBuilder->expects($this->once()) ->method('setFrom') ->with($sender) @@ -901,6 +909,10 @@ public function testSendPasswordReminderEmail() ->method('setTemplateVars') ->with(['customer' => $this->customerSecure, 'store' => $this->store]) ->willReturnSelf(); + $this->transportBuilder->expects($this->once()) + ->method('setScopeId') + ->with($customerStoreId) + ->willReturnSelf(); $this->transportBuilder->expects($this->once()) ->method('setFrom') ->with($sender) @@ -1031,6 +1043,10 @@ protected function prepareEmailSend($email, $templateIdentifier, $sender, $store ->method('setTemplateVars') ->with(['customer' => $this->customerSecure, 'store' => $this->store]) ->willReturnSelf(); + $this->transportBuilder->expects($this->any()) + ->method('setScopeId') + ->with($storeId) + ->willReturnSelf(); $this->transportBuilder->expects($this->any()) ->method('setFrom') ->with($sender) diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php index 6631703b2ada7..1096868dafab6 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php @@ -215,6 +215,7 @@ public function testSendNewAccountEmailWithoutStoreId() 'setTemplateIdentifier', 'setTemplateOptions', 'setTemplateVars', + 'setScopeId', 'setFrom', 'addTo', ]; diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 68b52264a3f6d..a208274a2cc8d 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -654,6 +654,7 @@ public function sendConfirmationRequestEmail() $this->inlineTranslation->suspend(); + $storeId = $this->_storeManager->getStore()->getId(); $this->_transportBuilder->setTemplateIdentifier( $this->_scopeConfig->getValue( self::XML_PATH_CONFIRM_EMAIL_TEMPLATE, @@ -662,10 +663,12 @@ public function sendConfirmationRequestEmail() )->setTemplateOptions( [ 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, - 'store' => $this->_storeManager->getStore()->getId(), + 'store' => $storeId, ] )->setTemplateVars( ['subscriber' => $this, 'store' => $this->_storeManager->getStore()] + )->setScopeId( + $storeId )->setFrom( $this->_scopeConfig->getValue( self::XML_PATH_CONFIRM_EMAIL_IDENTITY, @@ -707,6 +710,7 @@ public function sendConfirmationSuccessEmail() $this->inlineTranslation->suspend(); + $storeId = $this->_storeManager->getStore()->getId(); $this->_transportBuilder->setTemplateIdentifier( $this->_scopeConfig->getValue( self::XML_PATH_SUCCESS_EMAIL_TEMPLATE, @@ -715,10 +719,12 @@ public function sendConfirmationSuccessEmail() )->setTemplateOptions( [ 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, - 'store' => $this->_storeManager->getStore()->getId(), + 'store' => $storeId, ] )->setTemplateVars( ['subscriber' => $this] + )->setScopeId( + $storeId )->setFrom( $this->_scopeConfig->getValue( self::XML_PATH_SUCCESS_EMAIL_IDENTITY, @@ -759,6 +765,7 @@ public function sendUnsubscriptionEmail() $this->inlineTranslation->suspend(); + $storeId = $this->_storeManager->getStore()->getId(); $this->_transportBuilder->setTemplateIdentifier( $this->_scopeConfig->getValue( self::XML_PATH_UNSUBSCRIBE_EMAIL_TEMPLATE, @@ -767,10 +774,12 @@ public function sendUnsubscriptionEmail() )->setTemplateOptions( [ 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, - 'store' => $this->_storeManager->getStore()->getId(), + 'store' => $storeId, ] )->setTemplateVars( ['subscriber' => $this] + )->setScopeId( + $storeId )->setFrom( $this->_scopeConfig->getValue( self::XML_PATH_UNSUBSCRIBE_EMAIL_IDENTITY, diff --git a/app/code/Magento/ProductAlert/Model/Email.php b/app/code/Magento/ProductAlert/Model/Email.php index 1c70d7ff4a8e1..1eb884e1d82b2 100644 --- a/app/code/Magento/ProductAlert/Model/Email.php +++ b/app/code/Magento/ProductAlert/Model/Email.php @@ -374,6 +374,8 @@ public function send() 'customerName' => $this->_customerHelper->getCustomerName($this->_customer), 'alertGrid' => $alertGrid, ] + )->setScopeId( + $storeId )->setFrom( $this->_scopeConfig->getValue( self::XML_PATH_EMAIL_IDENTITY, diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php index ed58a69c91cd7..bed8b15ca65fa 100644 --- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php +++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php @@ -95,6 +95,7 @@ public function sendCopyTo() */ protected function configureEmailTemplate() { + $this->transportBuilder->setScopeId($this->identityContainer->getStore()->getStoreId()); $this->transportBuilder->setTemplateIdentifier($this->templateContainer->getTemplateId()); $this->transportBuilder->setTemplateOptions($this->templateContainer->getTemplateOptions()); $this->transportBuilder->setTemplateVars($this->templateContainer->getTemplateVars()); diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index 5ef75598b2f96..2f3ab181e6458 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -15,6 +15,7 @@ use Magento\Sales\Api\Data\ShippingAssignmentInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\InputException; +use Magento\Framework\Api\SortOrder; /** * Repository class for @see OrderInterface @@ -96,15 +97,25 @@ public function get($id) */ public function getList(\Magento\Framework\Api\SearchCriteria $searchCriteria) { - //@TODO: fix search logic /** @var \Magento\Sales\Api\Data\OrderSearchResultInterface $searchResult */ $searchResult = $this->searchResultFactory->create(); foreach ($searchCriteria->getFilterGroups() as $filterGroup) { - foreach ($filterGroup->getFilters() as $filter) { - $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; - $searchResult->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); - } + $this->addFilterGroupToCollection($filterGroup, $searchResult); + } + + $sortOrders = $searchCriteria->getSortOrders(); + if ($sortOrders === null) { + $sortOrders = []; + } + /** @var \Magento\Framework\Api\SortOrder $sortOrder */ + foreach ($sortOrders as $sortOrder) { + $field = $sortOrder->getField(); + $searchResult->addOrder( + $field, + ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' + ); } + $searchResult->setSearchCriteria($searchCriteria); $searchResult->setCurPage($searchCriteria->getCurrentPage()); $searchResult->setPageSize($searchCriteria->getPageSize()); foreach ($searchResult->getItems() as $order) { @@ -202,4 +213,28 @@ private function getShippingAssignmentBuilderDependency() } return $this->shippingAssignmentBuilder; } + + /** + * Helper function that adds a FilterGroup to the collection. + * + * @param \Magento\Framework\Api\Search\FilterGroup $filterGroup + * @param \Magento\Sales\Api\Data\OrderSearchResultInterface $searchResult + * @return void + * @throws \Magento\Framework\Exception\InputException + */ + protected function addFilterGroupToCollection( + \Magento\Framework\Api\Search\FilterGroup $filterGroup, + \Magento\Sales\Api\Data\OrderSearchResultInterface $searchResult + ) { + $fields = []; + $conditions = []; + foreach ($filterGroup->getFilters() as $filter) { + $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; + $conditions[] = [$condition => $filter->getValue()]; + $fields[] = $filter->getField(); + } + if ($fields) { + $searchResult->addFieldToFilter($fields, $conditions); + } + } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php index 1c0e42b28695f..8d5575fbdea9d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php @@ -29,6 +29,11 @@ class SenderBuilderTest extends \PHPUnit_Framework_TestCase */ protected $transportBuilder; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $storeMock; + protected function setUp() { $templateId = 'test_template_id'; @@ -58,7 +63,7 @@ protected function setUp() [ 'getEmailIdentity', 'getCustomerEmail', 'getCustomerName', 'getTemplateOptions', 'getEmailCopyTo', - 'getCopyMethod' + 'getCopyMethod', 'getStore' ], [], '', @@ -99,6 +104,9 @@ protected function setUp() $this->identityContainerMock->expects($this->once()) ->method('getEmailIdentity') ->will($this->returnValue($emailIdentity)); + $this->identityContainerMock->expects($this->once()) + ->method('getStore') + ->will($this->returnValue($this->storeMock)); $this->transportBuilder->expects($this->once()) ->method('setFrom') ->with($this->equalTo($emailIdentity)); diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php new file mode 100644 index 0000000000000..90b11f6e7fd2b --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -0,0 +1,117 @@ +objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $className = 'Magento\Sales\Model\ResourceModel\Metadata'; + $this->metadata = $this->getMock($className, [], [], '', false); + + $className = 'Magento\Sales\Api\Data\OrderSearchResultInterfaceFactory'; + $this->searchResultFactory = $this->getMock($className, ['create'], [], '', false); + + $this->model = $this->objectManager->getObject( + '\Magento\Sales\Model\OrderRepository', + [ + 'metadata' => $this->metadata, + 'searchResultFactory' => $this->searchResultFactory, + ] + ); + } + + /** + * TODO: Cover with unit tests the other methods in the repository + * test GetList + */ + public function testGetList() + { + $fieldName = 'field'; + $searchCriteriaMock = $this->getMock('Magento\Framework\Api\SearchCriteria', [], [], '', false); + + $collectionMock = $this->getMock('Magento\Sales\Model\ResourceModel\Order\Collection', [], [], '', false); + + $filterGroupMock = $this->getMock('\Magento\Framework\Api\Search\FilterGroup', [], [], '', false); + $filterGroupFilterMock = $this->getMock('\Magento\Framework\Api\Filter', [], [], '', false); + $sortOrderMock = $this->getMock('\Magento\Framework\Api\SortOrder', [], [], '', false); + $itemsMock = $this->getMock('Magento\Sales\Model\Order', [], [], '', false); + + $extensionAttributes = $this->getMock( + '\Magento\Sales\Api\Data\OrderExtension', + ['getShippingAssignments'], + [], + '', + false + ); + $shippingAssignmentBuilder = $this->getMock( + '\Magento\Sales\Model\Order\ShippingAssignmentBuilder', + [], + [], + '', + false + ); + + $itemsMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributes); + $extensionAttributes->expects($this->any()) + ->method('getShippingAssignments') + ->willReturn($shippingAssignmentBuilder); + + $this->searchResultFactory->expects($this->once())->method('create')->willReturn($collectionMock); + + $searchCriteriaMock->expects($this->once())->method('getFilterGroups')->willReturn([$filterGroupMock]); + $filterGroupMock->expects($this->once())->method('getFilters')->willReturn([$filterGroupFilterMock]); + $filterGroupFilterMock->expects($this->exactly(2))->method('getConditionType')->willReturn('eq'); + $filterGroupFilterMock->expects($this->atLeastOnce())->method('getField')->willReturn($fieldName); + $filterGroupFilterMock->expects($this->once())->method('getValue')->willReturn('value'); + $sortOrderMock->expects($this->once())->method('getDirection'); + $searchCriteriaMock->expects($this->once())->method('getSortOrders')->willReturn([$sortOrderMock]); + $sortOrderMock->expects($this->atLeastOnce())->method('getField')->willReturn($fieldName); + $collectionMock->expects($this->once())->method('addFieldToFilter') + ->willReturn(SortOrder::SORT_ASC); + $collectionMock->expects($this->once())->method('addOrder')->with($fieldName, 'DESC'); + $searchCriteriaMock->expects($this->once())->method('getCurrentPage')->willReturn(4); + $collectionMock->expects($this->once())->method('setCurPage')->with(4); + $searchCriteriaMock->expects($this->once())->method('getPageSize')->willReturn(42); + $collectionMock->expects($this->once())->method('setPageSize')->with(42); + $collectionMock->expects($this->once())->method('getItems')->willReturn([$itemsMock]); + + $this->assertEquals($collectionMock, $this->model->getList($searchCriteriaMock)); + } +} diff --git a/app/code/Magento/Theme/Controller/Result/MessagePlugin.php b/app/code/Magento/Theme/Controller/Result/MessagePlugin.php new file mode 100644 index 0000000000000..ab9ae23133946 --- /dev/null +++ b/app/code/Magento/Theme/Controller/Result/MessagePlugin.php @@ -0,0 +1,129 @@ +cookieManager = $cookieManager; + $this->cookieMetadataFactory = $cookieMetadataFactory; + $this->messageManager = $messageManager; + $this->jsonHelper = $jsonHelper; + $this->interpretationStrategy = $interpretationStrategy; + } + + /** + * @param ResultInterface $subject + * @param ResultInterface $result + * @return ResultInterface + */ + public function afterRenderResult( + ResultInterface $subject, + ResultInterface $result + ) { + if (!($subject instanceof Json)) { + $publicCookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata(); + $publicCookieMetadata->setDurationOneYear(); + $publicCookieMetadata->setPath('/'); + $publicCookieMetadata->setHttpOnly(false); + $this->cookieManager->setPublicCookie( + self::MESSAGES_COOKIES_NAME, + $this->jsonHelper->jsonEncode($this->getMessages()), + $publicCookieMetadata + ); + } + + return $result; + } + + /** + * Return messages array and clean message manager messages + * + * @return array + */ + protected function getMessages() + { + $messages = $this->getCookiesMessages(); + /** @var MessageInterface $message */ + foreach ($this->messageManager->getMessages(true)->getItems() as $message) { + $messages[] = [ + 'type' => $message->getType(), + 'text' => $this->interpretationStrategy->interpret($message), + ]; + } + return $messages; + } + + /** + * Return messages stored in cookies + * + * @return array + */ + protected function getCookiesMessages() + { + try { + $messages = $this->jsonHelper->jsonDecode( + $this->cookieManager->getCookie(self::MESSAGES_COOKIES_NAME, $this->jsonHelper->jsonEncode([])) + ); + if (!is_array($messages)) { + $messages = []; + } + } catch (\Zend_Json_Exception $e) { + $messages = []; + } + return $messages; + } +} diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php new file mode 100644 index 0000000000000..33ddec92b1fbd --- /dev/null +++ b/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php @@ -0,0 +1,415 @@ +cookieManagerMock = $this->getMockBuilder(CookieManagerInterface::class) + ->getMockForAbstractClass(); + $this->cookieMetadataFactoryMock = $this->getMockBuilder(CookieMetadataFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->managerMock = $this->getMockBuilder(ManagerInterface::class) + ->getMockForAbstractClass(); + $this->interpretationStrategyMock = $this->getMockBuilder(InterpretationStrategyInterface::class) + ->getMockForAbstractClass(); + $this->dataMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new MessagePlugin( + $this->cookieManagerMock, + $this->cookieMetadataFactoryMock, + $this->managerMock, + $this->interpretationStrategyMock, + $this->dataMock + ); + } + + public function testAfterRenderResultJson() + { + /** @var Json|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cookieManagerMock->expects($this->never()) + ->method('setPublicCookie'); + + $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); + } + + public function testAfterRenderResult() + { + + $existingMessages = [ + [ + 'type' => 'message0type', + 'text' => 'message0text', + ], + ]; + $messageType = 'message1type'; + $messageText = 'message1text'; + $messages = [ + [ + 'type' => $messageType, + 'text' => $messageText, + ], + ]; + $messages = array_merge($existingMessages, $messages); + + /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var PublicCookieMetadata|\PHPUnit_Framework_MockObject_MockObject $cookieMetadataMock */ + $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cookieMetadataFactoryMock->expects($this->once()) + ->method('createPublicCookieMetadata') + ->willReturn($cookieMetadataMock); + + $this->cookieManagerMock->expects($this->once()) + ->method('setPublicCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode($messages), + $cookieMetadataMock + ); + $this->cookieManagerMock->expects($this->once()) + ->method('getCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode([]) + ) + ->willReturn(\Zend_Json::encode($existingMessages)); + + $this->dataMock->expects($this->any()) + ->method('jsonDecode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::decode($data); + } + ); + $this->dataMock->expects($this->any()) + ->method('jsonEncode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::encode($data); + } + ); + + /** @var MessageInterface|\PHPUnit_Framework_MockObject_MockObject $messageMock */ + $messageMock = $this->getMockBuilder(MessageInterface::class) + ->getMock(); + $messageMock->expects($this->once()) + ->method('getType') + ->willReturn($messageType); + + $this->interpretationStrategyMock->expects($this->once()) + ->method('interpret') + ->with($messageMock) + ->willReturn($messageText); + + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('getItems') + ->willReturn([$messageMock]); + + $this->managerMock->expects($this->once()) + ->method('getMessages') + ->with(true, null) + ->willReturn($collectionMock); + + $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); + } + + public function testAfterRenderResultWithoutExisting() + { + $messageType = 'message1type'; + $messageText = 'message1text'; + $messages = [ + [ + 'type' => $messageType, + 'text' => $messageText, + ], + ]; + + /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var PublicCookieMetadata|\PHPUnit_Framework_MockObject_MockObject $cookieMetadataMock */ + $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cookieMetadataFactoryMock->expects($this->once()) + ->method('createPublicCookieMetadata') + ->willReturn($cookieMetadataMock); + + $this->cookieManagerMock->expects($this->once()) + ->method('setPublicCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode($messages), + $cookieMetadataMock + ); + $this->cookieManagerMock->expects($this->once()) + ->method('getCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode([]) + ) + ->willReturn(\Zend_Json::encode([])); + + $this->dataMock->expects($this->any()) + ->method('jsonDecode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::decode($data); + } + ); + $this->dataMock->expects($this->any()) + ->method('jsonEncode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::encode($data); + } + ); + + /** @var MessageInterface|\PHPUnit_Framework_MockObject_MockObject $messageMock */ + $messageMock = $this->getMockBuilder(MessageInterface::class) + ->getMock(); + $messageMock->expects($this->once()) + ->method('getType') + ->willReturn($messageType); + + $this->interpretationStrategyMock->expects($this->once()) + ->method('interpret') + ->with($messageMock) + ->willReturn($messageText); + + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('getItems') + ->willReturn([$messageMock]); + + $this->managerMock->expects($this->once()) + ->method('getMessages') + ->with(true, null) + ->willReturn($collectionMock); + + $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); + } + + public function testAfterRenderResultWithWrongJson() + { + $messageType = 'message1type'; + $messageText = 'message1text'; + $messages = [ + [ + 'type' => $messageType, + 'text' => $messageText, + ], + ]; + + /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var PublicCookieMetadata|\PHPUnit_Framework_MockObject_MockObject $cookieMetadataMock */ + $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cookieMetadataFactoryMock->expects($this->once()) + ->method('createPublicCookieMetadata') + ->willReturn($cookieMetadataMock); + + $this->cookieManagerMock->expects($this->once()) + ->method('setPublicCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode($messages), + $cookieMetadataMock + ); + $this->cookieManagerMock->expects($this->once()) + ->method('getCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode([]) + ) + ->willReturn(\Zend_Json::encode([])); + + $this->dataMock->expects($this->any()) + ->method('jsonDecode') + ->willThrowException(new \Zend_Json_Exception); + $this->dataMock->expects($this->any()) + ->method('jsonEncode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::encode($data); + } + ); + + /** @var MessageInterface|\PHPUnit_Framework_MockObject_MockObject $messageMock */ + $messageMock = $this->getMockBuilder(MessageInterface::class) + ->getMock(); + $messageMock->expects($this->once()) + ->method('getType') + ->willReturn($messageType); + + $this->interpretationStrategyMock->expects($this->once()) + ->method('interpret') + ->with($messageMock) + ->willReturn($messageText); + + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('getItems') + ->willReturn([$messageMock]); + + $this->managerMock->expects($this->once()) + ->method('getMessages') + ->with(true, null) + ->willReturn($collectionMock); + + $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); + } + + public function testAfterRenderResultWithWrongArray() + { + $messageType = 'message1type'; + $messageText = 'message1text'; + $messages = [ + [ + 'type' => $messageType, + 'text' => $messageText, + ], + ]; + + /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var PublicCookieMetadata|\PHPUnit_Framework_MockObject_MockObject $cookieMetadataMock */ + $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cookieMetadataFactoryMock->expects($this->once()) + ->method('createPublicCookieMetadata') + ->willReturn($cookieMetadataMock); + + $this->cookieManagerMock->expects($this->once()) + ->method('setPublicCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode($messages), + $cookieMetadataMock + ); + $this->cookieManagerMock->expects($this->once()) + ->method('getCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + \Zend_Json::encode([]) + ) + ->willReturn(\Zend_Json::encode('string')); + + $this->dataMock->expects($this->any()) + ->method('jsonDecode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::decode($data); + } + ); + $this->dataMock->expects($this->any()) + ->method('jsonEncode') + ->willReturnCallback( + function ($data) { + return \Zend_Json::encode($data); + } + ); + + /** @var MessageInterface|\PHPUnit_Framework_MockObject_MockObject $messageMock */ + $messageMock = $this->getMockBuilder(MessageInterface::class) + ->getMock(); + $messageMock->expects($this->once()) + ->method('getType') + ->willReturn($messageType); + + $this->interpretationStrategyMock->expects($this->once()) + ->method('interpret') + ->with($messageMock) + ->willReturn($messageText); + + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('getItems') + ->willReturn([$messageMock]); + + $this->managerMock->expects($this->once()) + ->method('getMessages') + ->with(true, null) + ->willReturn($collectionMock); + + $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); + } +} diff --git a/app/code/Magento/Theme/etc/frontend/di.xml b/app/code/Magento/Theme/etc/frontend/di.xml index a260164e4f598..85086d694f378 100644 --- a/app/code/Magento/Theme/etc/frontend/di.xml +++ b/app/code/Magento/Theme/etc/frontend/di.xml @@ -23,4 +23,7 @@ + + + diff --git a/app/code/Magento/Theme/view/frontend/templates/messages.phtml b/app/code/Magento/Theme/view/frontend/templates/messages.phtml index 595c4fa7d3d6e..2bd2357a27e1a 100644 --- a/app/code/Magento/Theme/view/frontend/templates/messages.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/messages.phtml @@ -5,6 +5,14 @@ */ ?>
+
+
+
+
+
', (string)$message->getText()); - } - $this->assertTrue($isProductNamePresent, 'Product name was not found in session messages'); + $product = $this->productRepository->get('product-with-xss'); + $this->dispatch('catalog/product_compare/remove/product/' . $product->getEntityId() . '?nocookie=1'); + + $this->assertSessionMessages( + $this->equalTo( + ['You removed product <script>alert("xss");</script> from the comparison list.'] + ), + MessageInterface::TYPE_SUCCESS + ); } protected function _prepareCompareListWithProductNameXss() @@ -171,7 +170,8 @@ protected function _prepareCompareListWithProductNameXss() $item = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Product\Compare\Item' ); - $item->setVisitorId($visitor->getId())->setProductId(1)->save(); + $firstProductEntityId = $this->productRepository->get('product-with-xss')->getEntityId(); + $item->setVisitorId($visitor->getId())->setProductId($firstProductEntityId)->save(); \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( 'Magento\Customer\Model\Visitor' )->load( @@ -211,13 +211,15 @@ protected function _requireVisitorWithTwoProducts() $item = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Product\Compare\Item' ); - $item->setVisitorId($visitor->getId())->setProductId(1)->save(); + $firstProductEntityId = $this->productRepository->get('simple_product_1')->getEntityId(); + $secondProductEntityId = $this->productRepository->get('simple_product_2')->getEntityId(); + $item->setVisitorId($visitor->getId())->setProductId($firstProductEntityId)->save(); /** @var $item \Magento\Catalog\Model\Product\Compare\Item */ $item = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( 'Magento\Catalog\Model\Product\Compare\Item' ); - $item->setVisitorId($visitor->getId())->setProductId(2)->save(); + $item->setVisitorId($visitor->getId())->setProductId($secondProductEntityId)->save(); \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( 'Magento\Customer\Model\Visitor' @@ -225,7 +227,7 @@ protected function _requireVisitorWithTwoProducts() $visitor->getId() ); - $this->_assertCompareListEquals([1, 2]); + $this->_assertCompareListEquals([$firstProductEntityId, $secondProductEntityId]); } protected function _requireCustomerWithTwoProducts() @@ -262,25 +264,28 @@ protected function _requireCustomerWithTwoProducts() ->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)) ->save(); + $firstProductEntityId = $this->productRepository->get('simple_product_1')->getEntityId(); + $secondProductEntityId = $this->productRepository->get('simple_product_2')->getEntityId(); + /** @var $item \Magento\Catalog\Model\Product\Compare\Item */ $item = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create('Magento\Catalog\Model\Product\Compare\Item'); $item->setVisitorId($visitor->getId()) ->setCustomerId(1) - ->setProductId(1) + ->setProductId($firstProductEntityId) ->save(); /** @var $item \Magento\Catalog\Model\Product\Compare\Item */ $item = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create('Magento\Catalog\Model\Product\Compare\Item'); $item->setVisitorId($visitor->getId()) - ->setProductId(2) + ->setProductId($secondProductEntityId) ->save(); \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Customer\Model\Visitor') ->load($visitor->getId()); - $this->_assertCompareListEquals([1, 2]); + $this->_assertCompareListEquals([$firstProductEntityId, $secondProductEntityId]); } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_xss.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_xss.php index 515c180c062a5..bb982398f8d39 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_xss.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_xss.php @@ -16,7 +16,7 @@ )->setName( '' )->setSku( - '' + 'product-with-xss' )->setPrice( 10 )->setDescription( diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Adminhtml/Paypal/ReportsTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Adminhtml/Paypal/ReportsTest.php index c8660cb759bde..c18880325bb41 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Adminhtml/Paypal/ReportsTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Adminhtml/Paypal/ReportsTest.php @@ -23,7 +23,7 @@ public function testFetchAction() { $this->dispatch('backend/paypal/paypal_reports/fetch'); $this->assertSessionMessages( - $this->equalTo(['We can\'t fetch reports from "login@127.0.0.1."']), + $this->equalTo(['We can\'t fetch reports from "login@127.0.0.1."']), \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php index 527650f1b02dc..e409a55c504b5 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Wishlist\Controller; -use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; - class IndexTest extends \Magento\TestFramework\TestCase\AbstractController { /** @@ -83,29 +81,29 @@ public function testItemColumnBlock() */ public function testAddActionProductNameXss() { - $this->dispatch('wishlist/index/add/product/1?nocookie=1'); - $messages = $this->_messages->getMessages()->getItems(); - $isProductNamePresent = false; + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get('Magento\Framework\Data\Form\FormKey'); + $this->getRequest()->setPostValue([ + 'form_key' => $formKey->getFormKey(), + ]); + + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Catalog\Api\ProductRepositoryInterface'); + + $product = $productRepository->get('product-with-xss'); - /** @var InterpretationStrategyInterface $interpretationStrategy */ - $interpretationStrategy = $this->_objectManager->create( - 'Magento\Framework\View\Element\Message\InterpretationStrategyInterface' + $this->dispatch('wishlist/index/add/product/' . $product->getId() . '?nocookie=1'); + + $this->assertSessionMessages( + $this->equalTo( + [ + "\n<script>alert("xss");</script> has been added to your Wish List. " + . 'Click here to continue shopping.', + ] + ), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS ); - foreach ($messages as $message) { - if ( - strpos( - $interpretationStrategy->interpret($message), - '<script>alert("xss");</script>' - ) !== false - ) { - $isProductNamePresent = true; - } - $this->assertNotContains( - '', - $interpretationStrategy->interpret($message) - ); - } - $this->assertTrue($isProductNamePresent, 'Product name was not found in session messages'); } /** diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/FilesystemTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/FilesystemTest.php index c7d0394f07c1b..09cf6b711f4d0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/FilesystemTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/FilesystemTest.php @@ -8,6 +8,7 @@ namespace Magento\Test\Legacy; use Magento\Framework\Component\ComponentRegistrar; +use Magento\Framework\Filesystem\Glob; class FilesystemTest extends \PHPUnit_Framework_TestCase { @@ -112,7 +113,7 @@ public function testObsoleteViewPaths() foreach ($pathsToCheck as $path => $allowed) { $allowedFiles = $allowed['allowed_files']; $allowedDirs = $allowed['allowed_dirs']; - $foundFiles = glob($path, GLOB_BRACE); + $foundFiles = Glob::glob($path, Glob::GLOB_BRACE); if (!$foundFiles) { $this->fail("Glob pattern returned empty result: {$path}"); } diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php index c4173ae95c603..8334f8e8dde9e 100644 --- a/lib/internal/Magento/Framework/App/Utility/Files.php +++ b/lib/internal/Magento/Framework/App/Utility/Files.php @@ -9,6 +9,7 @@ use Magento\Framework\Component\ComponentRegistrar; use Magento\Framework\Component\DirSearch; use Magento\Framework\View\Design\Theme\ThemePackageList; +use Magento\Framework\Filesystem\Glob; /** * A helper to gather specific kind of files in Magento application @@ -236,8 +237,8 @@ private function getPubFiles($flags) { if ($flags & self::INCLUDE_PUB_CODE) { return array_merge( - glob(BP . '/*.php', GLOB_NOSORT), - glob(BP . '/pub/*.php', GLOB_NOSORT) + Glob::glob(BP . '/*.php', Glob::GLOB_NOSORT), + Glob::glob(BP . '/pub/*.php', Glob::GLOB_NOSORT) ); } return []; @@ -1191,9 +1192,9 @@ public static function getFiles(array $dirPatterns, $fileNamePattern, $recursive { $result = []; foreach ($dirPatterns as $oneDirPattern) { - $oneDirPattern = str_replace('\\', '/', $oneDirPattern); - $entriesInDir = glob("{$oneDirPattern}/{$fileNamePattern}", GLOB_NOSORT | GLOB_BRACE); - $subDirs = glob("{$oneDirPattern}/*", GLOB_ONLYDIR | GLOB_NOSORT | GLOB_BRACE); + $oneDirPattern = str_replace('\\', '/', $oneDirPattern); + $entriesInDir = Glob::glob("{$oneDirPattern}/{$fileNamePattern}", Glob::GLOB_NOSORT | Glob::GLOB_BRACE); + $subDirs = Glob::glob("{$oneDirPattern}/*", Glob::GLOB_ONLYDIR | Glob::GLOB_NOSORT | Glob::GLOB_BRACE); $filesInDir = array_diff($entriesInDir, $subDirs); if ($recursive) { @@ -1212,10 +1213,13 @@ public static function getFiles(array $dirPatterns, $fileNamePattern, $recursive */ public function getDiConfigs($asDataSet = false) { - $primaryConfigs = glob(BP . '/app/etc/{di.xml,*/di.xml}', GLOB_BRACE); + $primaryConfigs = Glob::glob(BP . '/app/etc/{di.xml,*/di.xml}', Glob::GLOB_BRACE); $moduleConfigs = []; foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) { - $moduleConfigs = array_merge($moduleConfigs, glob($moduleDir . '/etc/{di,*/di}.xml', GLOB_BRACE)); + $moduleConfigs = array_merge( + $moduleConfigs, + Glob::glob($moduleDir . '/etc/{di,*/di}.xml', Glob::GLOB_BRACE) + ); } $configs = array_merge($primaryConfigs, $moduleConfigs); @@ -1383,7 +1387,7 @@ public function getModulePhpFiles($module, $asDataSet = true) $key = __METHOD__ . "/{$module}"; if (!isset(self::$_cache[$key])) { $files = self::getFiles( - [$this->componentRegistrar->getPath(ComponentRegistrar::MODULE, 'Magento_'. $module)], + [$this->componentRegistrar->getPath(ComponentRegistrar::MODULE, 'Magento_' . $module)], '*.php' ); self::$_cache[$key] = $files; @@ -1456,7 +1460,7 @@ public function readLists($globPattern) * Note that glob() for directories will be returned as is, * but passing directory is supported by the tools (phpcpd, phpmd, phpcs) */ - $files = glob($this->getPathToSource() . '/' . $pattern, GLOB_BRACE); + $files = Glob::glob($this->getPathToSource() . '/' . $pattern, Glob::GLOB_BRACE); } else { throw new \UnexpectedValueException( "Incorrect pattern record '$pattern'. Supported formats: " @@ -1501,7 +1505,7 @@ private function getPathByComponentPattern($componentType, $componentName, $path } else { $componentDir = $this->componentRegistrar->getPath($type, $componentName); if (!empty($componentDir)) { - $files = array_merge($files, glob($componentDir . '/' . $pathPattern, GLOB_BRACE)); + $files = array_merge($files, Glob::glob($componentDir . '/' . $pathPattern, Glob::GLOB_BRACE)); } } } diff --git a/lib/internal/Magento/Framework/Filesystem/Driver/File.php b/lib/internal/Magento/Framework/Filesystem/Driver/File.php index acf9d48b09221..2b081e6f54ca4 100644 --- a/lib/internal/Magento/Framework/Filesystem/Driver/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Driver/File.php @@ -9,6 +9,7 @@ use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Filesystem\Glob; /** * Class File @@ -268,7 +269,7 @@ public function search($pattern, $path) { clearstatcache(); $globPattern = rtrim($path, '/') . '/' . ltrim($pattern, '/'); - $result = @glob($globPattern, GLOB_BRACE); + $result = Glob::glob($globPattern, Glob::GLOB_BRACE); return is_array($result) ? $result : []; } diff --git a/lib/internal/Magento/Framework/Filesystem/Glob.php b/lib/internal/Magento/Framework/Filesystem/Glob.php new file mode 100644 index 0000000000000..f5a4fbeed0770 --- /dev/null +++ b/lib/internal/Magento/Framework/Filesystem/Glob.php @@ -0,0 +1,33 @@ +scopeId = $scopeId; + return $this; + } + /** * Set mail from address * @@ -164,7 +179,7 @@ public function setReplyTo($email, $name = null) */ public function setFrom($from) { - $result = $this->_senderResolver->resolve($from); + $result = $this->_senderResolver->resolve($from, $this->scopeId); $this->message->setFrom($result['email'], $result['name']); return $this; } @@ -242,6 +257,7 @@ protected function reset() $this->templateIdentifier = null; $this->templateVars = null; $this->templateOptions = null; + $this->scopeId = null; return $this; } diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/ClassesScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/ClassesScanner.php index ab3288fed4b53..74413dc5273df 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/ClassesScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/ClassesScanner.php @@ -6,7 +6,7 @@ namespace Magento\Setup\Module\Di\Code\Reader; use Magento\Framework\Exception\FileSystemException; -use Zend\Code\Scanner\FileScanner; +use Magento\Setup\Module\Di\Code\Reader\FileScanner; class ClassesScanner implements ClassesScannerInterface { diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php new file mode 100644 index 0000000000000..aacca3fbe71a5 --- /dev/null +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php @@ -0,0 +1,367 @@ +isScanned) { + return; + } + + if (!$this->tokens) { + throw new \Zend\Code\Exception\RuntimeException('No tokens were provided'); + } + + /** + * Define PHP 5.4 'trait' token constant. + */ + if (!defined('T_TRAIT')) { + define('T_TRAIT', 42001); + } + + /** + * Variables & Setup + */ + + $tokens = &$this->tokens; // localize + $infos = &$this->infos; // localize + $tokenIndex = null; + $token = null; + $this->tokenType = null; + $tokenContent = null; + $tokenLine = null; + $namespace = null; + $docCommentIndex = false; + $infoIndex = 0; + + /* + * MACRO creation + */ + $MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenContent, &$tokenLine) { + $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; + if (!isset($tokens[$tokenIndex])) { + $token = false; + $tokenContent = false; + $this->tokenType = false; + $tokenLine = false; + + return false; + } + if (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"') { + do { + $tokenIndex++; + } while (!(is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"')); + } + $token = $tokens[$tokenIndex]; + if (is_array($token)) { + list($this->tokenType, $tokenContent, $tokenLine) = $token; + } else { + $this->tokenType = null; + $tokenContent = $token; + } + + return $tokenIndex; + }; + $MACRO_TOKEN_LOGICAL_START_INDEX = function () use (&$tokenIndex, &$docCommentIndex) { + return ($docCommentIndex === false) ? $tokenIndex : $docCommentIndex; + }; + $MACRO_DOC_COMMENT_START = function () use (&$tokenIndex, &$docCommentIndex) { + $docCommentIndex = $tokenIndex; + + return $docCommentIndex; + }; + $MACRO_DOC_COMMENT_VALIDATE = function () use (&$docCommentIndex) { + static $validTrailingTokens = null; + if ($validTrailingTokens === null) { + $validTrailingTokens = array(T_WHITESPACE, T_FINAL, T_ABSTRACT, T_INTERFACE, T_CLASS, T_FUNCTION); + } + if ($docCommentIndex !== false && !in_array($this->tokenType, $validTrailingTokens)) { + $docCommentIndex = false; + } + + return $docCommentIndex; + }; + $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) { + $infos[$infoIndex]['tokenEnd'] = $tokenIndex; + $infos[$infoIndex]['lineEnd'] = $tokenLine; + $infoIndex++; + + return $infoIndex; + }; + + /** + * START FINITE STATE MACHINE FOR SCANNING TOKENS + */ + + // Initialize token + $MACRO_TOKEN_ADVANCE(); + + SCANNER_TOP: + + if ($token === false) { + goto SCANNER_END; + } + + // Validate current doc comment index + $MACRO_DOC_COMMENT_VALIDATE(); + + switch ($this->tokenType) { + + case T_DOC_COMMENT: + + $MACRO_DOC_COMMENT_START(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_NAMESPACE: + + $infos[$infoIndex] = array( + 'type' => 'namespace', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $token[2], + 'lineEnd' => null, + 'namespace' => null, + ); + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_NAMESPACE_TOP: + + if ($this->tokenType === null && $tokenContent === ';' || $tokenContent === '{') { + goto SCANNER_NAMESPACE_END; + } + + if ($this->tokenType === T_WHITESPACE) { + goto SCANNER_NAMESPACE_CONTINUE; + } + + if ($this->tokenType === T_NS_SEPARATOR || $this->tokenType === T_STRING) { + $infos[$infoIndex]['namespace'] .= $tokenContent; + } + + SCANNER_NAMESPACE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_NAMESPACE_TOP; + + SCANNER_NAMESPACE_END: + + $namespace = $infos[$infoIndex]['namespace']; + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_USE: + + $infos[$infoIndex] = array( + 'type' => 'use', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'namespace' => $namespace, + 'statements' => array(0 => array('use' => null, + 'as' => null)), + ); + + $useStatementIndex = 0; + $useAsContext = false; + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_USE_TOP: + + if ($this->tokenType === null) { + if ($tokenContent === ';') { + goto SCANNER_USE_END; + } elseif ($tokenContent === ',') { + $useAsContext = false; + $useStatementIndex++; + $infos[$infoIndex]['statements'][$useStatementIndex] = array('use' => null, + 'as' => null); + } + } + + // ANALYZE + if ($this->tokenType !== null) { + if ($this->tokenType == T_AS) { + $useAsContext = true; + goto SCANNER_USE_CONTINUE; + } + + if ($this->tokenType == T_NS_SEPARATOR || $this->tokenType == T_STRING) { + if ($useAsContext == false) { + $infos[$infoIndex]['statements'][$useStatementIndex]['use'] .= $tokenContent; + } else { + $infos[$infoIndex]['statements'][$useStatementIndex]['as'] = $tokenContent; + } + } + } + + SCANNER_USE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_USE_TOP; + + SCANNER_USE_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_INCLUDE: + case T_INCLUDE_ONCE: + case T_REQUIRE: + case T_REQUIRE_ONCE: + + // Static for performance + static $includeTypes = array( + T_INCLUDE => 'include', + T_INCLUDE_ONCE => 'include_once', + T_REQUIRE => 'require', + T_REQUIRE_ONCE => 'require_once' + ); + + $infos[$infoIndex] = array( + 'type' => 'include', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'includeType' => $includeTypes[$tokens[$tokenIndex][0]], + 'path' => '', + ); + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_INCLUDE_TOP: + + if ($this->tokenType === null && $tokenContent === ';') { + goto SCANNER_INCLUDE_END; + } + + $infos[$infoIndex]['path'] .= $tokenContent; + + SCANNER_INCLUDE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_INCLUDE_TOP; + + SCANNER_INCLUDE_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_FUNCTION: + case T_FINAL: + case T_ABSTRACT: + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + + $infos[$infoIndex] = array( + 'type' => ($this->tokenType === T_FUNCTION) ? 'function' : 'class', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'namespace' => $namespace, + 'uses' => $this->getUsesNoScan($namespace), + 'name' => null, + 'shortName' => null, + ); + + $classBraceCount = 0; + + // start processing with current token + + SCANNER_CLASS_TOP: + + // process the name + if ($infos[$infoIndex]['shortName'] == '' + && (($this->tokenType === T_CLASS || $this->tokenType === T_INTERFACE || $this->tokenType === T_TRAIT) && $infos[$infoIndex]['type'] === 'class' + || ($this->tokenType === T_FUNCTION && $infos[$infoIndex]['type'] === 'function')) + ) { + $infos[$infoIndex]['shortName'] = $tokens[$tokenIndex + 2][1]; + $infos[$infoIndex]['name'] = (($namespace !== null) ? $namespace . '\\' : '') . $infos[$infoIndex]['shortName']; + } + + if ($this->tokenType === null) { + if ($tokenContent == '{') { + $classBraceCount++; + } + if ($tokenContent == '}') { + $classBraceCount--; + if ($classBraceCount === 0) { + goto SCANNER_CLASS_END; + } + } + } + + SCANNER_CLASS_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_CLASS_TOP; + + SCANNER_CLASS_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + + } + + SCANNER_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + /** + * END FINITE STATE MACHINE FOR SCANNING TOKENS + */ + + $this->isScanned = true; + } +}