diff --git a/app/code/Magento/Analytics/Api/Data/LinkInterface.php b/app/code/Magento/Analytics/Api/Data/LinkInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6597dff868b9ffa780e7aea39c1f5dc41ebe97db --- /dev/null +++ b/app/code/Magento/Analytics/Api/Data/LinkInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Api\Data; + +/** + * Interface LinkInterface + * + * Represents link with collected data and initialized vector for decryption. + */ +interface LinkInterface +{ + /** + * @return string + */ + public function getUrl(); + + /** + * @return string + */ + public function getInitializationVector(); +} diff --git a/app/code/Magento/Analytics/Api/LinkProviderInterface.php b/app/code/Magento/Analytics/Api/LinkProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6ee43a423337ee5c78f4dc496421c68ea36b84b1 --- /dev/null +++ b/app/code/Magento/Analytics/Api/LinkProviderInterface.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Api; + +/** + * Provides link to file with collected report data. + */ +interface LinkProviderInterface +{ + /** + * @return \Magento\Analytics\Api\Data\LinkInterface + */ + public function get(); +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/AdditionalComment.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/AdditionalComment.php new file mode 100644 index 0000000000000000000000000000000000000000..c66996d839c09d3529b2647b44fc26df7786cfc7 --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/AdditionalComment.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Block\Adminhtml\System\Config; + +/** + * Provides field with additional information + */ +class AdditionalComment extends \Magento\Config\Block\System\Config\Form\Field +{ + /** + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $html = '<div class="config-additional-comment-title">' . $element->getLabel() . '</div>'; + $html .= '<div class="config-additional-comment-content">' . $element->getComment() . '</div>'; + return $this->decorateRowHtml($element, $html); + } + + /** + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @param string $html + * @return string + */ + private function decorateRowHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element, $html) + { + return sprintf( + '<tr id="row_%s"><td colspan="3"><div class="config-additional-comment">%s</div></td></tr>', + $element->getHtmlId(), + $html + ); + } +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php new file mode 100644 index 0000000000000000000000000000000000000000..c4118792255cd82370f1589f4460de74db0ba216 --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Block\Adminhtml\System\Config; + +/** + * Provides label with default Time Zone + */ +class CollectionTimeLabel extends \Magento\Config\Block\System\Config\Form\Field +{ + /** + * Add default time zone to comment + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $timeZoneCode = $this->_localeDate->getConfigTimezone(); + $getLongTimeZoneName = \IntlTimeZone::createTimeZone($timeZoneCode)->getDisplayName(); + $element->setData( + 'comment', + sprintf("%s (%s)", $getLongTimeZoneName, $timeZoneCode) + ); + return parent::render($element); + } +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/SubscriptionStatusLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/SubscriptionStatusLabel.php new file mode 100644 index 0000000000000000000000000000000000000000..c09213c7f009d96a6cb49723fc39fc38e64e0d38 --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/SubscriptionStatusLabel.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Block\Adminhtml\System\Config; + +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Backend\Block\Template\Context; + +/** + * Class SubscriptionStatusLabel. + * + * Provides labels for subscription status + * Status can be reviewed in System Configuration + */ +class SubscriptionStatusLabel extends \Magento\Config\Block\System\Config\Form\Field +{ + /** + * @var SubscriptionStatusProvider + */ + private $subscriptionStatusProvider; + + /** + * SubscriptionStatusLabel constructor. + * + * @param Context $context + * @param SubscriptionStatusProvider $labelStatusProvider + * @param array $data + */ + public function __construct( + Context $context, + SubscriptionStatusProvider $labelStatusProvider, + array $data = [] + ) { + parent::__construct($context, $data); + $this->subscriptionStatusProvider = $labelStatusProvider; + } + + /** + * Add Subscription status to comment + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @return string + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $element->setData( + 'comment', + $this->prepareLabelValue() + ); + return parent::render($element); + } + + /** + * Prepare label for subscription status + * + * @return string + */ + private function prepareLabelValue() + { + return __('Subscription status') . ': ' . __($this->subscriptionStatusProvider->getStatus()); + } +} diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/Vertical.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/Vertical.php new file mode 100644 index 0000000000000000000000000000000000000000..99606e10f99d9403445493878441f94512a42b03 --- /dev/null +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/Vertical.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Block\Adminhtml\System\Config; + +/** + * Provides vertical select with additional information and style customization + */ +class Vertical extends \Magento\Config\Block\System\Config\Form\Field +{ + /** + * @inheritdoc + */ + public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + $html = '<div class="config-vertical-title">' . $element->getHint() . '</div>'; + $html .= '<div class="config-vertical-comment">' . $element->getComment() . '</div>'; + return $this->decorateRowHtml($element, $html); + } + + /** + * Decorates row HTML for custom element style + * + * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @param string $html + * @return string + */ + private function decorateRowHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element, $html) + { + $rowHtml = sprintf('<tr><td colspan="4">%s</td></tr>', $html); + $rowHtml .= sprintf( + '<tr id="row_%s"><td class="label config-vertical-label">%s</td><td class="value">%s</td></tr>', + $element->getHtmlId(), + $element->getLabelHtml($element->getHtmlId(), "[WEBSITE]"), + $element->getElementHtml() + ); + return $rowHtml; + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php b/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php new file mode 100644 index 0000000000000000000000000000000000000000..a90a971cf41b492e29e29ad6f74d5b3885c31d1d --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/BIEssentials/SignUp.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Controller\Adminhtml\BIEssentials; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Class SignUp + * + * Provides link to BI Essentials signup + */ +class SignUp extends Action +{ + /** + * Path to config value with URL to BI Essentials sign-up page. + * + * @var string + */ + private $urlBIEssentialsConfigPath = 'analytics/url/bi_essentials'; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @param Context $context + * @param ScopeConfigInterface $config + */ + public function __construct( + Context $context, + ScopeConfigInterface $config + ) { + $this->config = $config; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::bi_essentials'); + } + + /** + * Provides link to BI Essentials signup + * + * @return \Magento\Framework\Controller\AbstractResult + */ + public function execute() + { + return $this->resultRedirectFactory->create()->setUrl( + $this->config->getValue($this->urlBIEssentialsConfigPath) + ); + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php new file mode 100644 index 0000000000000000000000000000000000000000..1b0e5c92420deadc80e2f1e7725989acd7b63f8f --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Reports/Show.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Controller\Adminhtml\Reports; + +use Magento\Analytics\Model\Exception\State\SubscriptionUpdateException; +use Magento\Analytics\Model\ReportUrlProvider; +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; + +/** + * Provide redirect to resource with reports. + */ +class Show extends Action +{ + /** + * @var ReportUrlProvider + */ + private $reportUrlProvider; + + /** + * @param Context $context + * @param ReportUrlProvider $reportUrlProvider + */ + public function __construct( + Context $context, + ReportUrlProvider $reportUrlProvider + ) { + $this->reportUrlProvider = $reportUrlProvider; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller. + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::analytics_settings'); + } + + /** + * Redirect to resource with reports. + * + * @return Redirect $resultRedirect + */ + public function execute() + { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + try { + $resultRedirect->setUrl($this->reportUrlProvider->getUrl()); + } catch (SubscriptionUpdateException $e) { + $this->getMessageManager()->addNoticeMessage($e->getMessage()); + $resultRedirect->setPath('adminhtml'); + } catch (LocalizedException $e) { + $this->getMessageManager()->addExceptionMessage($e, $e->getMessage()); + $resultRedirect->setPath('adminhtml'); + } catch (\Exception $e) { + $this->getMessageManager()->addExceptionMessage( + $e, + __('Sorry, there has been an error processing your request. Please try again later.') + ); + $resultRedirect->setPath('adminhtml'); + } + + return $resultRedirect; + } +} diff --git a/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Retry.php b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Retry.php new file mode 100644 index 0000000000000000000000000000000000000000..122cf74123cc99127dd528fa8ef917e67a9d5065 --- /dev/null +++ b/app/code/Magento/Analytics/Controller/Adminhtml/Subscription/Retry.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Controller\Adminhtml\Subscription; + +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; + +/** + * Retry subscription to Magento BI Advanced Reporting. + */ +class Retry extends Action +{ + /** + * Resource for managing subscription to Magento Analytics. + * + * @var SubscriptionHandler + */ + private $subscriptionHandler; + + /** + * @param Context $context + * @param SubscriptionHandler $subscriptionHandler + */ + public function __construct( + Context $context, + SubscriptionHandler $subscriptionHandler + ) { + $this->subscriptionHandler = $subscriptionHandler; + parent::__construct($context); + } + + /** + * Check admin permissions for this controller + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('Magento_Analytics::analytics_settings'); + } + + /** + * Retry process of subscription. + * + * @return Redirect + */ + public function execute() + { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + try { + $resultRedirect->setPath('adminhtml'); + $this->subscriptionHandler->processEnabled(); + } catch (LocalizedException $e) { + $this->getMessageManager()->addExceptionMessage($e, $e->getMessage()); + } catch (\Exception $e) { + $this->getMessageManager()->addExceptionMessage( + $e, + __('Sorry, there has been an error processing your request. Please try again later.') + ); + } + + return $resultRedirect; + } +} diff --git a/app/code/Magento/Analytics/Cron/CollectData.php b/app/code/Magento/Analytics/Cron/CollectData.php new file mode 100644 index 0000000000000000000000000000000000000000..ff0b3e4f67638b02a7bf9c72ab82058b4e9da101 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/CollectData.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Cron; + +use Magento\Analytics\Model\ExportDataHandlerInterface; +use Magento\Analytics\Model\SubscriptionStatusProvider; + +/** + * Cron for data collection by a schedule for MBI. + */ +class CollectData +{ + /** + * Resource for the handling of a new data collection. + * + * @var ExportDataHandlerInterface + */ + private $exportDataHandler; + + /** + * Resource which provides a status of subscription. + * + * @var SubscriptionStatusProvider + */ + private $subscriptionStatus; + + /** + * @param ExportDataHandlerInterface $exportDataHandler + * @param SubscriptionStatusProvider $subscriptionStatus + */ + public function __construct( + ExportDataHandlerInterface $exportDataHandler, + SubscriptionStatusProvider $subscriptionStatus + ) { + $this->exportDataHandler = $exportDataHandler; + $this->subscriptionStatus = $subscriptionStatus; + } + + /** + * @return bool + */ + public function execute() + { + if ($this->subscriptionStatus->getStatus() === SubscriptionStatusProvider::ENABLED) { + $this->exportDataHandler->prepareExportData(); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Cron/SignUp.php b/app/code/Magento/Analytics/Cron/SignUp.php new file mode 100644 index 0000000000000000000000000000000000000000..c17b9b8c381c33ddc856890a264ce73b6b22a7b4 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/SignUp.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Cron; + +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Analytics\Model\Connector; +use Magento\Framework\FlagManager; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; + +/** + * Class SignUp + */ +class SignUp +{ + /** + * @var Connector + */ + private $connector; + + /** + * @var WriterInterface + */ + private $configWriter; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * Reinitable Config Model. + * + * @var ReinitableConfigInterface + */ + private $reinitableConfig; + + /** + * @param Connector $connector + * @param WriterInterface $configWriter + * @param FlagManager $flagManager + * @param ReinitableConfigInterface $reinitableConfig + */ + public function __construct( + Connector $connector, + WriterInterface $configWriter, + FlagManager $flagManager, + ReinitableConfigInterface $reinitableConfig + ) { + $this->connector = $connector; + $this->configWriter = $configWriter; + $this->flagManager = $flagManager; + $this->reinitableConfig = $reinitableConfig; + } + + /** + * Execute scheduled subscription operation + * In case of failure writes message to notifications inbox + * + * @return bool + */ + public function execute() + { + $attemptsCount = $this->flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + + if (($attemptsCount === null) || ($attemptsCount <= 0)) { + $this->deleteAnalyticsCronExpr(); + $this->flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + return false; + } + + $attemptsCount -= 1; + $this->flagManager->saveFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $attemptsCount); + $signUpResult = $this->connector->execute('signUp'); + if ($signUpResult === false) { + return false; + } + + $this->deleteAnalyticsCronExpr(); + $this->flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + return true; + } + + /** + * Delete cron schedule setting into config. + * + * Delete cron schedule setting for subscription handler into config and + * re-initialize config cache to avoid auto-generate new schedule items. + * + * @return bool + */ + private function deleteAnalyticsCronExpr() + { + $this->configWriter->delete(SubscriptionHandler::CRON_STRING_PATH); + $this->reinitableConfig->reinit(); + return true; + } +} diff --git a/app/code/Magento/Analytics/Cron/Update.php b/app/code/Magento/Analytics/Cron/Update.php new file mode 100644 index 0000000000000000000000000000000000000000..9062a7bac7551eab8685c1722705b1ef0c49ba04 --- /dev/null +++ b/app/code/Magento/Analytics/Cron/Update.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Cron; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector; +use Magento\Framework\FlagManager; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; + +/** + * Executes by cron schedule in case base url was changed + */ +class Update +{ + /** + * @var Connector + */ + private $connector; + + /** + * @var WriterInterface + */ + private $configWriter; + + /** + * @var ReinitableConfigInterface + */ + private $reinitableConfig; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @param Connector $connector + * @param WriterInterface $configWriter + * @param ReinitableConfigInterface $reinitableConfig + * @param FlagManager $flagManager + * @param AnalyticsToken $analyticsToken + */ + public function __construct( + Connector $connector, + WriterInterface $configWriter, + ReinitableConfigInterface $reinitableConfig, + FlagManager $flagManager, + AnalyticsToken $analyticsToken + ) { + $this->connector = $connector; + $this->configWriter = $configWriter; + $this->reinitableConfig = $reinitableConfig; + $this->flagManager = $flagManager; + $this->analyticsToken = $analyticsToken; + } + + /** + * Execute scheduled update operation + * + * @return bool + */ + public function execute() + { + $result = false; + $attemptsCount = $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + + if ($attemptsCount) { + $attemptsCount -= 1; + $result = $this->connector->execute('update'); + } + + if ($result || ($attemptsCount <= 0) || (!$this->analyticsToken->isTokenExist())) { + $this->flagManager + ->deleteFlag(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + $this->flagManager->deleteFlag(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE); + $this->configWriter->delete(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH); + $this->reinitableConfig->reinit(); + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/LICENSE.txt b/app/code/Magento/Analytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/Analytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Analytics/LICENSE_AFL.txt b/app/code/Magento/Analytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/Analytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/Analytics/Model/AnalyticsToken.php b/app/code/Magento/Analytics/Model/AnalyticsToken.php new file mode 100644 index 0000000000000000000000000000000000000000..ccec4d1bbe9588f9f53795bf9f5df1793721fc96 --- /dev/null +++ b/app/code/Magento/Analytics/Model/AnalyticsToken.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; + +/** + * Model for handling Magento BI token value into config. + */ +class AnalyticsToken +{ + /** + * Path to value of Magento BI token into config. + */ + private $tokenPath = 'analytics/general/token'; + + /** + * Reinitable Config Model. + * + * @var ReinitableConfigInterface + */ + private $reinitableConfig; + + /** + * Scope config model. + * + * @var ScopeConfigInterface + */ + private $config; + + /** + * Service which allows to write values into config. + * + * @var WriterInterface + */ + private $configWriter; + + /** + * @param ReinitableConfigInterface $reinitableConfig + * @param ScopeConfigInterface $config + * @param WriterInterface $configWriter + */ + public function __construct( + ReinitableConfigInterface $reinitableConfig, + ScopeConfigInterface $config, + WriterInterface $configWriter + ) { + $this->reinitableConfig = $reinitableConfig; + $this->config = $config; + $this->configWriter = $configWriter; + } + + /** + * Get Magento BI token value. + * + * @return string|null + */ + public function getToken() + { + return $this->config->getValue($this->tokenPath); + } + + /** + * Stores Magento BI token value. + * + * @param string $value + * + * @return bool + */ + public function storeToken($value) + { + $this->configWriter->save($this->tokenPath, $value); + $this->reinitableConfig->reinit(); + + return true; + } + + /** + * Check Magento BI token value exist. + * + * @return bool + */ + public function isTokenExist() + { + return (bool)$this->getToken(); + } +} diff --git a/app/code/Magento/Analytics/Model/Config.php b/app/code/Magento/Analytics/Model/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..ba508187b4b9f19daa347a047f775c825340d5fa --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model; + +use Magento\Framework\Config\DataInterface; + +/** + * Config of Analytics. + */ +class Config implements ConfigInterface +{ + /** + * @var DataInterface + */ + private $data; + + /** + * @param DataInterface $data + */ + public function __construct(DataInterface $data) + { + $this->data = $data; + } + + /** + * Get config value by key. + * + * @param string|null $key + * @param string|null $default + * @return array + */ + public function get($key = null, $default = null) + { + return $this->data->get($key, $default); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Baseurl/SubscriptionUpdateHandler.php b/app/code/Magento/Analytics/Model/Config/Backend/Baseurl/SubscriptionUpdateHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..6e6f008d49f7e1b6bca82c2503459c825a21bba0 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Baseurl/SubscriptionUpdateHandler.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model\Config\Backend\Baseurl; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; + +/** + * Class for processing of change of Base URL. + */ +class SubscriptionUpdateHandler +{ + /** + * Flag code for a reserve counter to update subscription. + */ + const SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE = 'analytics_link_subscription_update_reverse_counter'; + + /** + * Config path for schedule setting of update handler. + */ + const UPDATE_CRON_STRING_PATH = "crontab/default/jobs/analytics_update/schedule/cron_expr"; + + /** + * Flag code for the previous Base URL. + */ + const PREVIOUS_BASE_URL_FLAG_CODE = 'analytics_previous_base_url'; + + /** + * Max value for a reserve counter to update subscription. + * + * @var int + */ + private $attemptsInitValue = 48; + + /** + * @var WriterInterface + */ + private $configWriter; + + /** + * Cron expression for a update handler. + * + * @var string + */ + private $cronExpression = '0 * * * *'; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @var ReinitableConfigInterface + */ + private $reinitableConfig; + + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @param AnalyticsToken $analyticsToken + * @param FlagManager $flagManager + * @param ReinitableConfigInterface $reinitableConfig + * @param WriterInterface $configWriter + */ + public function __construct( + AnalyticsToken $analyticsToken, + FlagManager $flagManager, + ReinitableConfigInterface $reinitableConfig, + WriterInterface $configWriter + ) { + $this->analyticsToken = $analyticsToken; + $this->flagManager = $flagManager; + $this->reinitableConfig = $reinitableConfig; + $this->configWriter = $configWriter; + } + + /** + * Activate process of subscription update handling. + * + * @param string $url + * @return bool + */ + public function processUrlUpdate(string $url) + { + if ($this->analyticsToken->isTokenExist()) { + if (!$this->flagManager->getFlagData(self::PREVIOUS_BASE_URL_FLAG_CODE)) { + $this->flagManager->saveFlag(self::PREVIOUS_BASE_URL_FLAG_CODE, $url); + } + + $this->flagManager + ->saveFlag(self::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue); + $this->configWriter->save(self::UPDATE_CRON_STRING_PATH, $this->cronExpression); + $this->reinitableConfig->reinit(); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php new file mode 100644 index 0000000000000000000000000000000000000000..e26ad01fc74bfc2b5d21c4a5d7a32b09d7eb16e9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/CollectionTime.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Config\Backend; + +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\App\Config\Value; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Model\Context; +use Magento\Framework\Model\ResourceModel\AbstractResource; +use Magento\Framework\Registry; + +/** + * Config value backend model. + */ +class CollectionTime extends Value +{ + /** + * The path to config setting of schedule of collection data cron. + */ + const CRON_SCHEDULE_PATH = 'crontab/default/jobs/analytics_collect_data/schedule/cron_expr'; + + /** + * @var WriterInterface + */ + private $configWriter; + + /** + * @param Context $context + * @param Registry $registry + * @param ScopeConfigInterface $config + * @param TypeListInterface $cacheTypeList + * @param WriterInterface $configWriter + * @param AbstractResource|null $resource + * @param AbstractDb|null $resourceCollection + * @param array $data + */ + public function __construct( + Context $context, + Registry $registry, + ScopeConfigInterface $config, + TypeListInterface $cacheTypeList, + WriterInterface $configWriter, + AbstractResource $resource = null, + AbstractDb $resourceCollection = null, + array $data = [] + ) { + $this->configWriter = $configWriter; + parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); + } + + /** + * {@inheritdoc} + * + * {@inheritdoc}. Set schedule setting for cron. + * + * @return Value + */ + public function afterSave() + { + $result = preg_match('#(?<hour>\d{2}),(?<min>\d{2}),(?<sec>\d{2})#', $this->getValue(), $time); + + if (!$result) { + throw new LocalizedException(__('Time value has an unsupported format')); + } + + $cronExprArray = [ + $time['min'], # Minute + $time['hour'], # Hour + '*', # Day of the Month + '*', # Month of the Year + '*', # Day of the Week + ]; + + $cronExprString = join(' ', $cronExprArray); + + try { + $this->configWriter->save(self::CRON_SCHEDULE_PATH, $cronExprString); + } catch (\Exception $e) { + $this->_logger->error($e->getMessage()); + throw new LocalizedException(__('Cron settings can\'t be saved')); + } + + return parent::afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php new file mode 100644 index 0000000000000000000000000000000000000000..ac97f2a843e6184883d2b2559c36b290095c680b --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Config\Backend; + +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Value; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Model\Context; +use Magento\Framework\Model\ResourceModel\AbstractResource; +use Magento\Framework\Registry; + +/** + * Config value backend model. + */ +class Enabled extends Value +{ + /** + * Path to field subscription enabled into config structure. + */ + const XML_ENABLED_CONFIG_STRUCTURE_PATH = 'analytics/general/enabled'; + + /** + * Service for processing of activation/deactivation MBI subscription. + * + * @var SubscriptionHandler + */ + private $subscriptionHandler; + + /** + * @param Context $context + * @param Registry $registry + * @param ScopeConfigInterface $config + * @param TypeListInterface $cacheTypeList + * @param SubscriptionHandler $subscriptionHandler + * @param AbstractResource|null $resource + * @param AbstractDb|null $resourceCollection + * @param array $data + */ + public function __construct( + Context $context, + Registry $registry, + ScopeConfigInterface $config, + TypeListInterface $cacheTypeList, + SubscriptionHandler $subscriptionHandler, + AbstractResource $resource = null, + AbstractDb $resourceCollection = null, + array $data = [] + ) { + $this->subscriptionHandler = $subscriptionHandler; + parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); + } + + /** + * Add additional handling after config value was saved. + * + * @return Value + * @throws LocalizedException + */ + public function afterSave() + { + try { + if ($this->isValueChanged()) { + $enabled = $this->getData('value'); + + if ($enabled) { + $this->subscriptionHandler->processEnabled(); + } else { + $this->subscriptionHandler->processDisabled(); + } + } + } catch (\Exception $e) { + $this->_logger->error($e->getMessage()); + throw new LocalizedException(__('There was an error save new configuration value.')); + } + + return parent::afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php b/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..4b125949948c6f557b10a847a6d7e422d82d558b --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Enabled/SubscriptionHandler.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Config\Backend\Enabled; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\CollectionTime; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; + +/** + * Class for processing of activation/deactivation MBI subscription. + */ +class SubscriptionHandler +{ + /** + * Flag code for reserve counter of attempts to subscribe. + */ + const ATTEMPTS_REVERSE_COUNTER_FLAG_CODE = 'analytics_link_attempts_reverse_counter'; + + /** + * Config path for schedule setting of subscription handler. + */ + const CRON_STRING_PATH = 'crontab/default/jobs/analytics_subscribe/schedule/cron_expr'; + + /** + * Config value for schedule setting of subscription handler. + */ + const CRON_EXPR_ARRAY = [ + '0', # Minute + '*', # Hour + '*', # Day of the Month + '*', # Month of the Year + '*', # Day of the Week + ]; + + /** + * Max value for reserve counter of attempts to subscribe. + * + * @var int + */ + private $attemptsInitValue = 24; + + /** + * Service which allows to write values into config. + * + * @var WriterInterface + */ + private $configWriter; + + /** + * Flag Manager. + * + * @var FlagManager + */ + private $flagManager; + + /** + * Model for handling Magento BI token value. + * + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var ReinitableConfigInterface + */ + private $reinitableConfig; + + /** + * @param WriterInterface $configWriter + * @param FlagManager $flagManager + * @param AnalyticsToken $analyticsToken + * @param ReinitableConfigInterface $reinitableConfig + */ + public function __construct( + WriterInterface $configWriter, + FlagManager $flagManager, + AnalyticsToken $analyticsToken, + ReinitableConfigInterface $reinitableConfig + ) { + $this->configWriter = $configWriter; + $this->flagManager = $flagManager; + $this->analyticsToken = $analyticsToken; + $this->reinitableConfig = $reinitableConfig; + } + + /** + * Processing of activation MBI subscription. + * + * Activate process of subscription handling if Analytics token is not received. + * + * @return bool + */ + public function processEnabled() + { + if (!$this->analyticsToken->isTokenExist()) { + $this->setCronSchedule(); + $this->setAttemptsFlag(); + $this->reinitableConfig->reinit(); + } + + return true; + } + + /** + * Set cron schedule setting into config for activation of subscription process. + * + * @return bool + */ + private function setCronSchedule() + { + $this->configWriter->save(self::CRON_STRING_PATH, join(' ', self::CRON_EXPR_ARRAY)); + return true; + } + + /** + * Set flag as reserve counter of attempts subscription operation. + * + * @return bool + */ + private function setAttemptsFlag() + { + return $this->flagManager + ->saveFlag(self::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue); + } + + /** + * Processing of deactivation MBI subscription. + * + * Disable data collection + * and interrupt subscription handling if Analytics token is not received. + * + * @return bool + */ + public function processDisabled() + { + $this->disableCollectionData(); + + if (!$this->analyticsToken->isTokenExist()) { + $this->unsetAttemptsFlag(); + } + + return true; + } + + /** + * Unset flag of attempts subscription operation. + * + * @return bool + */ + private function unsetAttemptsFlag() + { + return $this->flagManager + ->deleteFlag(self::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + } + + /** + * Unset schedule of collection data cron. + * + * @return bool + */ + private function disableCollectionData() + { + $this->configWriter->delete(CollectionTime::CRON_SCHEDULE_PATH); + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php b/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php new file mode 100644 index 0000000000000000000000000000000000000000..1aabbb91ddf874d9ebf5c92680fd26d77ae1b01a --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Backend/Vertical.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Config\Backend; + +use Magento\Framework\Exception\LocalizedException; + +/** + * A backend model for verticals configuration. + */ +class Vertical extends \Magento\Framework\App\Config\Value +{ + /** + * Handles the value of the selected vertical before saving. + * + * Note that the selected vertical should not be empty since + * it will cause distortion of the analytics reports. + * + * @return $this + * @throws LocalizedException if the value of the selected vertical is empty. + */ + public function beforeSave() + { + if (empty($this->getValue())) { + throw new LocalizedException(__('Please select a vertical.')); + } + + return $this; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Mapper.php b/app/code/Magento/Analytics/Model/Config/Mapper.php new file mode 100644 index 0000000000000000000000000000000000000000..504690b8e47636c70b12c9cdf5a1f7040c23023c --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Mapper.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model\Config; + +/** + * Transforms Analytics configuration data. + */ +class Mapper +{ + /** + * Transforms Analytics configuration data. + * + * @param array $configData + * @return array $files + * $files = [ + * 'file_name' => [ + * 'name' => 'file_name', + * 'providers' => [ + * 'reportProvider' => [ + * 'name' => 'report_provider_name', + * 'class' => 'Magento\Analytics\ReportXml\ReportProvider', + * 'parameters' =>[ + * 'name' => 'report_name', + * ], + * ], + * 'customProvider' => [ + * 'name' => 'custom_provider_name', + * 'class' => 'Magento\Analytics\Model\CustomProvider', + * ], + * ], + * ] + * ]; + */ + public function execute($configData) + { + if (!isset($configData['config'][0]['file'])) { + return []; + } + + $files = []; + foreach ($configData['config'][0]['file'] as $fileData) { + /** just one set of providers is allowed by xsd */ + $providers = reset($fileData['providers']); + foreach ($providers as $providerType => $providerDataSet) { + /** just one set of provider data is allowed by xsd */ + $providerData = reset($providerDataSet); + /** just one set of parameters is allowed by xsd */ + $providerData['parameters'] = !empty($providerData['parameters']) + ? reset($providerData['parameters']) + : []; + $providerData['parameters'] = array_map( + 'reset', + $providerData['parameters'] + ); + $providers[$providerType] = $providerData; + } + $files[$fileData['name']] = $fileData; + $files[$fileData['name']]['providers'] = $providers; + } + return $files; + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Reader.php b/app/code/Magento/Analytics/Model/Config/Reader.php new file mode 100644 index 0000000000000000000000000000000000000000..8980e31627717372dc5225ab20881dde740354a8 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Reader.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Config; + +use Magento\Framework\Config\ReaderInterface; + +/** + * Composite reader for config. + */ +class Reader implements ReaderInterface +{ + /** + * @var ReaderInterface[] + */ + private $readers; + + /** + * @var Mapper + */ + private $mapper; + + /** + * @param Mapper $mapper + * @param ReaderInterface[] $readers + */ + public function __construct( + Mapper $mapper, + $readers = [] + ) { + $this->mapper = $mapper; + $this->readers = $readers; + } + + /** + * Read configuration scope. + * + * @param string|null $scope + * @return array + */ + public function read($scope = null) + { + $data = []; + foreach ($this->readers as $reader) { + $data = array_merge_recursive($data, $reader->read($scope)); + } + + return $this->mapper->execute($data); + } +} diff --git a/app/code/Magento/Analytics/Model/Config/Source/Vertical.php b/app/code/Magento/Analytics/Model/Config/Source/Vertical.php new file mode 100644 index 0000000000000000000000000000000000000000..c9d9582ea7c7a1c9ed788fd62d89f1f6e1328bb4 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Config/Source/Vertical.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Config\Source; + +/** + * A source model for verticals configuration. + * + * Prepares and provides options for a selector of verticals which is located + * in the corresponding configuration menu of the Admin area. + */ +class Vertical implements \Magento\Framework\Option\ArrayInterface +{ + /** + * The list of possible verticals. + * + * This list is configured via di.xml and may be extended or changed + * in any module if it is needed. + * + * It is supposed that the list may be changed in each Magento release. + * + * @var array + */ + private $verticals; + + /** + * @param array $verticals + */ + public function __construct(array $verticals) + { + $this->verticals = $verticals; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + $result = [ + ['value' => '', 'label' => __('--Please Select--')] + ]; + + foreach ($this->verticals as $vertical) { + $result[] = ['value' => $vertical, 'label' => __($vertical)]; + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/ConfigInterface.php b/app/code/Magento/Analytics/Model/ConfigInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..caaa2e100c1c7f6c7cd77c2dc6acdb73450e12c9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ConfigInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model; + +/** + * Interface for Analytics Config. + */ +interface ConfigInterface +{ + /** + * Get config value by key. + * + * @param string|null $key + * @param string|null $default + * @return array + */ + public function get($key = null, $default = null); +} diff --git a/app/code/Magento/Analytics/Model/Connector.php b/app/code/Magento/Analytics/Model/Connector.php new file mode 100644 index 0000000000000000000000000000000000000000..23b0ffa213b6ecf8219807a3809fc6cd73ec70b5 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\ObjectManagerInterface; + +/** + * A connector to external services. + * + * Aggregates and executes commands which perform requests to external services. + */ +class Connector +{ + /** + * A list of possible commands. + * + * An associative array in format: 'command_name' => 'command_class_name'. + * + * The list may be configured in each module via '/etc/di.xml'. + * + * @var string[] + */ + private $commands; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param array $commands + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + array $commands, + ObjectManagerInterface $objectManager + ) { + $this->commands = $commands; + $this->objectManager = $objectManager; + } + + /** + * Executes a command in accordance with the given name. + * + * @param string $commandName + * @return bool + * @throws NotFoundException if the command is not found. + */ + public function execute($commandName) + { + if (!array_key_exists($commandName, $this->commands)) { + throw new NotFoundException(__('Command was not found.')); + } + + /** @var \Magento\Analytics\Model\Connector\CommandInterface $command */ + $command = $this->objectManager->create($this->commands[$commandName]); + + return $command->execute(); + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/CommandInterface.php b/app/code/Magento/Analytics/Model/Connector/CommandInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..7a8774fe3dba9fc76216efc67afeb619b5b3e530 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/CommandInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector; + +/** + * Introduces family of integration calls. + * Each implementation represents call to external service. + */ +interface CommandInterface +{ + /** + * Execute call to external service + * Information about destination and arguments appears from config + * + * @return bool + */ + public function execute(); +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php b/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php new file mode 100644 index 0000000000000000000000000000000000000000..c223bb1f3b07d274368954c6b54962b5c87c8794 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http\Client; + +use Magento\Analytics\Model\Connector\Http\ConverterInterface; +use Psr\Log\LoggerInterface; +use Magento\Framework\HTTP\Adapter\CurlFactory; +use Magento\Analytics\Model\Connector\Http\ResponseFactory; + +/** + * A CURL HTTP client. + * + * Sends requests via a CURL adapter. + */ +class Curl implements \Magento\Analytics\Model\Connector\Http\ClientInterface +{ + /** + * @var CurlFactory + */ + private $curlFactory; + + /** + * @var ResponseFactory + */ + private $responseFactory; + + /** + * @var ConverterInterface + */ + private $converter; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param CurlFactory $curlFactory + * @param ResponseFactory $responseFactory + * @param ConverterInterface $converter + * @param LoggerInterface $logger + */ + public function __construct( + CurlFactory $curlFactory, + ResponseFactory $responseFactory, + ConverterInterface $converter, + LoggerInterface $logger + ) { + $this->curlFactory = $curlFactory; + $this->responseFactory = $responseFactory; + $this->converter = $converter; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function request($method, $url, array $body = [], array $headers = [], $version = '1.1') + { + $response = new \Zend_Http_Response(0, []); + + try { + $curl = $this->curlFactory->create(); + $headers = $this->applyContentTypeHeaderFromConverter($headers); + + $curl->write($method, $url, $version, $headers, $this->converter->toBody($body)); + + $result = $curl->read(); + + if ($curl->getErrno()) { + $this->logger->critical( + new \Exception( + sprintf( + 'MBI service CURL connection error #%s: %s', + $curl->getErrno(), + $curl->getError() + ) + ) + ); + + return $response; + } + + $response = $this->responseFactory->create($result); + } catch (\Exception $e) { + $this->logger->critical($e); + } + + return $response; + } + + /** + * @param array $headers + * + * @return array + */ + private function applyContentTypeHeaderFromConverter(array $headers) + { + $contentTypeHeaderKey = array_search($this->converter->getContentTypeHeader(), $headers); + if ($contentTypeHeaderKey === false) { + $headers[] = $this->converter->getContentTypeHeader(); + } + + return $headers; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a1e1f057684f60e11b0e356d63051d019d38f37b --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +/** + * An interface for an HTTP client. + * + * Sends requests via a proper adapter. + */ +interface ClientInterface +{ + /** + * Sends a request using given parameters. + * + * Returns an HTTP response object or FALSE in case of failure. + * + * @param string $method + * @param string $url + * @param array $body + * @param array $headers + * @param string $version + * + * @return \Zend_Http_Response + */ + public function request($method, $url, array $body = [], array $headers = [], $version = '1.1'); +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a6ce85cadc62f76111407750f777f1750918f899 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +/** + * Represents converter interface for http request and response body. + */ +interface ConverterInterface +{ + /** + * @param string $body + * + * @return array + */ + public function fromBody($body); + + /** + * @param array $data + * + * @return string + */ + public function toBody(array $data); + + /** + * @return string + */ + public function getContentTypeHeader(); +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php b/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php new file mode 100644 index 0000000000000000000000000000000000000000..4a0f27f245316baa2695adf4661e6414c357b4e4 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +/** + * Represents JSON converter for http request and response body. + */ +class JsonConverter implements ConverterInterface +{ + /** + * Content-Type HTTP header for json. + */ + const CONTENT_TYPE_HEADER = 'Content-Type: application/json'; + + /** + * @param string $body + * + * @return array + */ + public function fromBody($body) + { + $decodedBody = json_decode($body, 1); + return $decodedBody === null ? [$body] : $decodedBody; + } + + /** + * @param array $data + * + * @return string + */ + public function toBody(array $data) + { + return json_encode($data); + } + + /** + * @return string + */ + public function getContentTypeHeader() + { + return self::CONTENT_TYPE_HEADER; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseFactory.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..77d23b88529a3a459c28e6eae97c49dd147d56a7 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseFactory.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +/** + * A factory for an HTTP response. + */ +class ResponseFactory +{ + /** + * Creates a new \Zend_Http_Response object from a string. + * + * @param string $response + * @return \Zend_Http_Response + */ + public function create($response) + { + return \Zend_Http_Response::fromString($response); + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseHandlerInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseHandlerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..4a6633f08da55687de90e90db4a336b3b3554435 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseHandlerInterface.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +/** + * Represents an interface for response handler which process response body. + */ +interface ResponseHandlerInterface +{ + /** + * @param array $responseBody + * @return bool|string + */ + public function handleResponse(array $responseBody); +} diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..ec198e4a3c40b746008b51b509385b658b31a740 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +/** + * Extract result from http response. Call response handler by status. + */ +class ResponseResolver +{ + /** + * @var ConverterInterface + */ + private $converter; + + /** + * @var array + */ + private $responseHandlers; + + /** + * @param ConverterInterface $converter + * @param ResponseHandlerInterface[] $responseHandlers + */ + public function __construct(ConverterInterface $converter, array $responseHandlers = []) + { + $this->converter = $converter; + $this->responseHandlers = $responseHandlers; + } + + /** + * @param \Zend_Http_Response $response + * + * @return bool|string + */ + public function getResult(\Zend_Http_Response $response) + { + $result = false; + $responseBody = $this->converter->fromBody($response->getBody()); + if (array_key_exists($response->getStatus(), $this->responseHandlers)) { + $result = $this->responseHandlers[$response->getStatus()]->handleResponse($responseBody); + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php b/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..f1a8ea6460f9d82057ddc9db17b6daf4ef6b2c2b --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\HTTP\ZendClient; +use Psr\Log\LoggerInterface; +use Magento\Store\Model\Store; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; + +/** + * Command notifies MBI about that data collection was finished. + */ +class NotifyDataChangedCommand implements CommandInterface +{ + /** + * @var string + */ + private $notifyDataChangedUrlPath = 'analytics/url/notify_data_changed'; + + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var Http\ClientInterface + */ + private $httpClient; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var ResponseResolver + */ + private $responseResolver; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * NotifyDataChangedCommand constructor. + * @param AnalyticsToken $analyticsToken + * @param Http\ClientInterface $httpClient + * @param ScopeConfigInterface $config + * @param ResponseResolver $responseResolver + * @param LoggerInterface $logger + */ + public function __construct( + AnalyticsToken $analyticsToken, + Http\ClientInterface $httpClient, + ScopeConfigInterface $config, + ResponseResolver $responseResolver, + LoggerInterface $logger + ) { + $this->analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->responseResolver = $responseResolver; + $this->logger = $logger; + } + + /** + * Notify MBI about that data collection was finished + * + * @return bool + */ + public function execute() + { + $result = false; + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->notifyDataChangedUrlPath), + [ + "access-token" => $this->analyticsToken->getToken(), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + $result = $this->responseResolver->getResult($response); + } + return (bool)$result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php new file mode 100644 index 0000000000000000000000000000000000000000..dfa283e10d070d713d5d11d712ea051527774459 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\HTTP\ZendClient; +use Magento\Store\Model\Store; +use Psr\Log\LoggerInterface; + +/** + * Representation of an 'OTP' request. + * + * The request is responsible for obtaining of an OTP from the MBI service. + * + * OTP (One-Time Password) is a password that is valid for short period of time + * and may be used only for one login session. + */ +class OTPRequest +{ + /** + * Resource for handling MBI token value. + * + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var Http\ClientInterface + */ + private $httpClient; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var ResponseResolver + */ + private $responseResolver; + + /** + * Path to the configuration value which contains + * an URL that provides an OTP. + * + * @var string + */ + private $otpUrlConfigPath = 'analytics/url/otp'; + + /** + * @param AnalyticsToken $analyticsToken + * @param Http\ClientInterface $httpClient + * @param ScopeConfigInterface $config + * @param ResponseResolver $responseResolver + * @param LoggerInterface $logger + */ + public function __construct( + AnalyticsToken $analyticsToken, + Http\ClientInterface $httpClient, + ScopeConfigInterface $config, + ResponseResolver $responseResolver, + LoggerInterface $logger + ) { + $this->analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->responseResolver = $responseResolver; + $this->logger = $logger; + } + + /** + * Performs obtaining of an OTP from the MBI service. + * + * Returns received OTP or FALSE in case of failure. + * + * @return string|false + */ + public function call() + { + $result = false; + + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->otpUrlConfigPath), + [ + "access-token" => $this->analyticsToken->getToken(), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + + $result = $this->responseResolver->getResult($response); + if (!$result) { + $this->logger->warning( + sprintf( + 'Obtaining of an OTP from the MBI service has been failed: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + ) + ); + } + } + + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/OTP.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/OTP.php new file mode 100644 index 0000000000000000000000000000000000000000..d9a672e81f43d6fc57f608445a4deb41cbff0a8e --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/OTP.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; + +/** + * Fetches OTP from body. + */ +class OTP implements ResponseHandlerInterface +{ + /** + * @param array $responseBody + * + * @return bool|string + */ + public function handleResponse(array $responseBody) + { + return !empty($responseBody['otp']) ? $responseBody['otp'] : false; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/ReSignUp.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/ReSignUp.php new file mode 100644 index 0000000000000000000000000000000000000000..c79630d6414d88fff1c09fc9fe5e1ee59f89a0a6 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/ReSignUp.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; +use Magento\Analytics\Model\SubscriptionStatusProvider; + +/** + * Removes stored token and triggers subscription process. + */ +class ReSignUp implements ResponseHandlerInterface +{ + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var SubscriptionHandler + */ + private $subscriptionHandler; + + /** + * @var SubscriptionStatusProvider + */ + private $subscriptionStatusProvider; + + /** + * @param AnalyticsToken $analyticsToken + * @param SubscriptionHandler $subscriptionHandler + * @param SubscriptionStatusProvider $subscriptionStatusProvider + */ + public function __construct( + AnalyticsToken $analyticsToken, + SubscriptionHandler $subscriptionHandler, + SubscriptionStatusProvider $subscriptionStatusProvider + ) { + $this->analyticsToken = $analyticsToken; + $this->subscriptionHandler = $subscriptionHandler; + $this->subscriptionStatusProvider = $subscriptionStatusProvider; + } + + /** + * @inheritdoc + */ + public function handleResponse(array $responseBody) + { + if ($this->subscriptionStatusProvider->getStatus() === SubscriptionStatusProvider::ENABLED) { + $this->analyticsToken->storeToken(null); + $this->subscriptionHandler->processEnabled(); + } + return false; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/SignUp.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/SignUp.php new file mode 100644 index 0000000000000000000000000000000000000000..b2261e418abc72c62274eb6a864db5e318916be5 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/SignUp.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Connector\Http\ConverterInterface; +use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; + +/** + * Stores access token to MBI that received in body. + */ +class SignUp implements ResponseHandlerInterface +{ + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var ConverterInterface + */ + private $converter; + + /** + * @param AnalyticsToken $analyticsToken + * @param ConverterInterface $converter + */ + public function __construct( + AnalyticsToken $analyticsToken, + ConverterInterface $converter + ) { + $this->analyticsToken = $analyticsToken; + $this->converter = $converter; + } + + /** + * @inheritdoc + */ + public function handleResponse(array $body) + { + if (isset($body['access-token']) && !empty($body['access-token'])) { + $this->analyticsToken->storeToken($body['access-token']); + return $body['access-token']; + } + + return false; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/ResponseHandler/Update.php b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/Update.php new file mode 100644 index 0000000000000000000000000000000000000000..73fc575ae282129e2c481fcf152003636c8841e3 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/ResponseHandler/Update.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; + +/** + * Return positive answer that request was finished successfully. + */ +class Update implements ResponseHandlerInterface +{ + /** + * @param array $responseBody + * + * @return bool|string + */ + public function handleResponse(array $responseBody) + { + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..a1f23637e04b18ffce0e6a8a350966c2f34a49bd --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Analytics\Model\IntegrationManager; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Psr\Log\LoggerInterface; +use Magento\Framework\HTTP\ZendClient; +use Magento\Store\Model\Store; + +/** + * Class SignUpCommand + * + * SignUp merchant for Free Tier project + */ +class SignUpCommand implements CommandInterface +{ + /** + * @var string + */ + private $signUpUrlPath = 'analytics/url/signup'; + + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var IntegrationManager + */ + private $integrationManager; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var Http\ClientInterface + */ + private $httpClient; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var ResponseResolver + */ + private $responseResolver; + + /** + * SignUpCommand constructor. + * + * @param AnalyticsToken $analyticsToken + * @param IntegrationManager $integrationManager + * @param ScopeConfigInterface $config + * @param Http\ClientInterface $httpClient + * @param LoggerInterface $logger + * @param ResponseResolver $responseResolver + */ + public function __construct( + AnalyticsToken $analyticsToken, + IntegrationManager $integrationManager, + ScopeConfigInterface $config, + Http\ClientInterface $httpClient, + LoggerInterface $logger, + ResponseResolver $responseResolver + ) { + $this->analyticsToken = $analyticsToken; + $this->integrationManager = $integrationManager; + $this->config = $config; + $this->httpClient = $httpClient; + $this->logger = $logger; + $this->responseResolver = $responseResolver; + } + + /** + * Executes signUp command + * + * During this call Magento generates or retrieves access token for the integration user + * In case successful generation Magento activates user and sends access token to MA + * As the response, Magento receives a token to MA + * Magento stores this token in System Configuration + * + * This method returns true in case of success + * + * @return bool + */ + public function execute() + { + $result = false; + $integrationToken = $this->integrationManager->generateToken(); + if ($integrationToken) { + $this->integrationManager->activateIntegration(); + $response = $this->httpClient->request( + ZendClient::POST, + $this->config->getValue($this->signUpUrlPath), + [ + "token" => $integrationToken->getData('token'), + "url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + ] + ); + + $result = $this->responseResolver->getResult($response); + if (!$result) { + $this->logger->warning( + sprintf( + 'Subscription for MBI service has been failed. An error occurred during token exchange: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + ) + ); + } + } + + return (bool)$result; + } +} diff --git a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..8f05f1107e87e6d6a4e3d5ed675822b19a89a9d2 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\HTTP\ZendClient; +use Magento\Store\Model\Store; +use Psr\Log\LoggerInterface; + +/** + * Class UpdateCommand + * Command executes in case change store url + */ +class UpdateCommand implements CommandInterface +{ + /** + * @var string + */ + private $updateUrlPath = 'analytics/url/update'; + + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var Http\ClientInterface + */ + private $httpClient; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @var ResponseResolver + */ + private $responseResolver; + + /** + * @param AnalyticsToken $analyticsToken + * @param Http\ClientInterface $httpClient + * @param ScopeConfigInterface $config + * @param LoggerInterface $logger + * @param FlagManager $flagManager + * @param ResponseResolver $responseResolver + */ + public function __construct( + AnalyticsToken $analyticsToken, + Http\ClientInterface $httpClient, + ScopeConfigInterface $config, + LoggerInterface $logger, + FlagManager $flagManager, + ResponseResolver $responseResolver + ) { + $this->analyticsToken = $analyticsToken; + $this->httpClient = $httpClient; + $this->config = $config; + $this->logger = $logger; + $this->flagManager = $flagManager; + $this->responseResolver = $responseResolver; + } + + /** + * Executes update request to MBI api in case store url was changed + * + * @return bool + */ + public function execute() + { + $result = false; + if ($this->analyticsToken->isTokenExist()) { + $response = $this->httpClient->request( + ZendClient::PUT, + $this->config->getValue($this->updateUrlPath), + [ + "url" => $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE), + "new-url" => $this->config->getValue(Store::XML_PATH_SECURE_BASE_URL), + "access-token" => $this->analyticsToken->getToken(), + ] + ); + $result = $this->responseResolver->getResult($response); + if (!$result) { + $this->logger->warning( + sprintf( + 'Update of the subscription for MBI service has been failed: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + ) + ); + } + } + + return (bool)$result; + } +} diff --git a/app/code/Magento/Analytics/Model/Cryptographer.php b/app/code/Magento/Analytics/Model/Cryptographer.php new file mode 100644 index 0000000000000000000000000000000000000000..6905eee372ae20952e0bdffb8dc911f4200f0618 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Cryptographer.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\Exception\LocalizedException; + +/** + * Class for encrypting data. + */ +class Cryptographer +{ + /** + * Resource for handling MBI token value. + * + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * Cipher method for encryption. + * + * @var string + */ + private $cipherMethod = 'AES-256-CBC'; + + /** + * @var EncodedContextFactory + */ + private $encodedContextFactory; + + /** + * @param AnalyticsToken $analyticsToken + * @param EncodedContextFactory $encodedContextFactory + */ + public function __construct( + AnalyticsToken $analyticsToken, + EncodedContextFactory $encodedContextFactory + ) { + $this->analyticsToken = $analyticsToken; + $this->encodedContextFactory = $encodedContextFactory; + } + + /** + * Encrypt input data. + * + * @param string $source + * @return EncodedContext + * @throws LocalizedException + */ + public function encode($source) + { + if (!is_string($source)) { + try { + $source = (string)$source; + } catch (\Exception $e) { + throw new LocalizedException(__('Input data must be string or convertible into string.')); + } + } elseif (!$source) { + throw new LocalizedException(__('Input data must be non-empty string.')); + } + if (!$this->validateCipherMethod($this->cipherMethod)) { + throw new LocalizedException(__('Not valid cipher method.')); + } + $initializationVector = $this->getInitializationVector(); + + $encodedContext = $this->encodedContextFactory->create([ + 'content' => openssl_encrypt( + $source, + $this->cipherMethod, + $this->getKey(), + OPENSSL_RAW_DATA, + $initializationVector + ), + 'initializationVector' => $initializationVector, + ]); + + return $encodedContext; + } + + /** + * Return key for encryption. + * + * @return string + * @throws LocalizedException + */ + private function getKey() + { + $token = $this->analyticsToken->getToken(); + if (!$token) { + throw new LocalizedException(__('Encryption key can\'t be empty.')); + } + return hash('sha256', $token); + } + + /** + * Return established cipher method. + * + * @return string + */ + private function getCipherMethod() + { + return $this->cipherMethod; + } + + /** + * Return each time generated random initialization vector which depends on the cipher method. + * + * @return string + */ + private function getInitializationVector() + { + $ivSize = openssl_cipher_iv_length($this->getCipherMethod()); + return openssl_random_pseudo_bytes($ivSize); + } + + /** + * Check that cipher method is allowed for encryption. + * + * @param string $cipherMethod + * @return bool + */ + private function validateCipherMethod($cipherMethod) + { + $methods = openssl_get_cipher_methods(); + return (false !== array_search($cipherMethod, $methods)); + } +} diff --git a/app/code/Magento/Analytics/Model/EncodedContext.php b/app/code/Magento/Analytics/Model/EncodedContext.php new file mode 100644 index 0000000000000000000000000000000000000000..5fb2d0c15aef7f4f28f012ac5895f10092421427 --- /dev/null +++ b/app/code/Magento/Analytics/Model/EncodedContext.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +/** + * Contain information about encrypted data. + */ +class EncodedContext +{ + /** + * Encrypted string. + * + * @var string + */ + private $content; + + /** + * Initialization vector that was used for encryption. + * + * @var string + */ + private $initializationVector; + + /** + * @param string $content + * @param string $initializationVector + */ + public function __construct($content, $initializationVector = '') + { + $this->content = $content; + $this->initializationVector = $initializationVector; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/Exception/State/SubscriptionUpdateException.php b/app/code/Magento/Analytics/Model/Exception/State/SubscriptionUpdateException.php new file mode 100644 index 0000000000000000000000000000000000000000..5d127037afea97e58f6c0d7271cd7d4ae82a1360 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Exception/State/SubscriptionUpdateException.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model\Exception\State; + +use Magento\Framework\Exception\LocalizedException; + +/** + * Analytics is in update subscription mode. + */ +class SubscriptionUpdateException extends LocalizedException +{ + +} diff --git a/app/code/Magento/Analytics/Model/ExportDataHandler.php b/app/code/Magento/Analytics/Model/ExportDataHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..b9d3b6340184ba36d84073022f124cc98764ebc8 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ExportDataHandler.php @@ -0,0 +1,203 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Archive; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; + +/** + * Class for the handling of a new data collection for MBI. + */ +class ExportDataHandler implements ExportDataHandlerInterface +{ + /** + * Subdirectory path for all temporary files. + * + * @var string + */ + private $subdirectoryPath = 'analytics/'; + + /** + * Filename of archive with collected data. + * + * @var string + */ + private $archiveName = 'data.tgz'; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var Archive + */ + private $archive; + + /** + * Resource for write data of reports into separate files. + * + * @var ReportWriterInterface + */ + private $reportWriter; + + /** + * Resource for encrypting data. + * + * @var Cryptographer + */ + private $cryptographer; + + /** + * Resource for registration a new file. + * + * @var FileRecorder + */ + private $fileRecorder; + + /** + * @param Filesystem $filesystem + * @param Archive $archive + * @param ReportWriterInterface $reportWriter + * @param Cryptographer $cryptographer + * @param FileRecorder $fileRecorder + */ + public function __construct( + Filesystem $filesystem, + Archive $archive, + ReportWriterInterface $reportWriter, + Cryptographer $cryptographer, + FileRecorder $fileRecorder + ) { + $this->filesystem = $filesystem; + $this->archive = $archive; + $this->reportWriter = $reportWriter; + $this->cryptographer = $cryptographer; + $this->fileRecorder = $fileRecorder; + } + + /** + * @inheritdoc + */ + public function prepareExportData() + { + try { + $tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); + + $this->prepareDirectory($tmpDirectory, $this->getTmpFilesDirRelativePath()); + $this->reportWriter->write($tmpDirectory, $this->getTmpFilesDirRelativePath()); + + $tmpFilesDirectoryAbsolutePath = $this->validateSource($tmpDirectory, $this->getTmpFilesDirRelativePath()); + $archiveAbsolutePath = $this->prepareFileDirectory($tmpDirectory, $this->getArchiveRelativePath()); + $this->pack( + $tmpFilesDirectoryAbsolutePath, + $archiveAbsolutePath + ); + + $this->validateSource($tmpDirectory, $this->getArchiveRelativePath()); + $this->fileRecorder->recordNewFile( + $this->cryptographer->encode($tmpDirectory->readFile($this->getArchiveRelativePath())) + ); + } finally { + $tmpDirectory->delete($this->getTmpFilesDirRelativePath()); + $tmpDirectory->delete($this->getArchiveRelativePath()); + } + + return true; + } + + /** + * Return relative path to a directory for temporary files with reports data. + * + * @return string + */ + private function getTmpFilesDirRelativePath() + { + return $this->subdirectoryPath . 'tmp/'; + } + + /** + * Return relative path to a directory for an archive. + * + * @return string + */ + private function getArchiveRelativePath() + { + return $this->subdirectoryPath . $this->archiveName; + } + + /** + * Clean up a directory. + * + * @param WriteInterface $directory + * @param string $path + * @return string + */ + private function prepareDirectory(WriteInterface $directory, $path) + { + $directory->delete($path); + + return $directory->getAbsolutePath($path); + } + + /** + * Remove a file and a create parent directory a file. + * + * @param WriteInterface $directory + * @param string $path + * @return string + */ + private function prepareFileDirectory(WriteInterface $directory, $path) + { + $directory->delete($path); + if (dirname($path) !== '.') { + $directory->create(dirname($path)); + } + + return $directory->getAbsolutePath($path); + } + + /** + * Packing data into an archive. + * + * @param string $source + * @param string $destination + * @return bool + */ + private function pack($source, $destination) + { + $this->archive->pack( + $source, + $destination, + is_dir($source) ?: false + ); + + return true; + } + + /** + * Validate that data source exist. + * + * Return absolute path in a validated data source. + * + * @param WriteInterface $directory + * @param string $path + * @return string + * @throws LocalizedException If source is not exist. + */ + private function validateSource(WriteInterface $directory, $path) + { + if (!$directory->isExist($path)) { + throw new LocalizedException(__('Source "%1" is not exist', $directory->getAbsolutePath($path))); + } + + return $directory->getAbsolutePath($path); + } +} diff --git a/app/code/Magento/Analytics/Model/ExportDataHandlerInterface.php b/app/code/Magento/Analytics/Model/ExportDataHandlerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..65efb33659c899dbabd65e10d8264e7e1d0eaede --- /dev/null +++ b/app/code/Magento/Analytics/Model/ExportDataHandlerInterface.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +/** + * The interface represents the type of classes that handling of a new data collection for MBI. + */ +interface ExportDataHandlerInterface +{ + /** + * Execute collecting new data for MBI. + * + * @return bool + */ + public function prepareExportData(); +} diff --git a/app/code/Magento/Analytics/Model/ExportDataHandlerNotification.php b/app/code/Magento/Analytics/Model/ExportDataHandlerNotification.php new file mode 100644 index 0000000000000000000000000000000000000000..34941ac9ae2f270ce8392eccd8f2a12e99b0d0d0 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ExportDataHandlerNotification.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +/** + * Class which add notification behaviour to classes that handling of a new data collection for MBI. + */ +class ExportDataHandlerNotification implements ExportDataHandlerInterface +{ + /** + * @var ExportDataHandler + */ + private $exportDataHandler; + + /** + * @var Connector + */ + private $analyticsConnector; + + /** + * ExportDataHandlerNotification constructor. + * + * @param ExportDataHandlerInterface $exportDataHandler + * @param Connector $connector + */ + public function __construct(ExportDataHandler $exportDataHandler, Connector $connector) + { + $this->exportDataHandler = $exportDataHandler; + $this->analyticsConnector = $connector; + } + + /** + * {@inheritdoc} + * Execute notification command. + * + * @return bool + */ + public function prepareExportData() + { + $result = $this->exportDataHandler->prepareExportData(); + $this->analyticsConnector->execute('notifyDataChanged'); + return $result; + } +} diff --git a/app/code/Magento/Analytics/Model/FileInfo.php b/app/code/Magento/Analytics/Model/FileInfo.php new file mode 100644 index 0000000000000000000000000000000000000000..19bdaf21b2a2048aa43ad4a4cc59202dcd60282e --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileInfo.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +/** + * Contain information about encrypted file. + */ +class FileInfo +{ + /** + * Initialization vector that was used for encryption. + * + * @var string + */ + private $initializationVector; + + /** + * Relative path to an encrypted file. + * + * @var string + */ + private $path; + + /** + * @param string $path + * @param string $initializationVector + */ + public function __construct($path = '', $initializationVector = '') + { + $this->path = $path; + $this->initializationVector = $initializationVector; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/FileInfoManager.php b/app/code/Magento/Analytics/Model/FileInfoManager.php new file mode 100644 index 0000000000000000000000000000000000000000..e37700e665420e26cade7d808e2ea4b21cf86966 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileInfoManager.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\FlagManager; + +/** + * Manage saving and loading FileInfo object. + */ +class FileInfoManager +{ + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @var FileInfoFactory + */ + private $fileInfoFactory; + + /** + * Flag code for a stored FileInfo object. + * + * @var string + */ + private $flagCode = 'analytics_file_info'; + + /** + * Parameters which have to be saved into encoded form. + * + * @var array + */ + private $encodedParameters = [ + 'initializationVector' + ]; + + /** + * @param FlagManager $flagManager + * @param FileInfoFactory $fileInfoFactory + */ + public function __construct( + FlagManager $flagManager, + FileInfoFactory $fileInfoFactory + ) { + $this->flagManager = $flagManager; + $this->fileInfoFactory = $fileInfoFactory; + } + + /** + * Save FileInfo object. + * + * @param FileInfo $fileInfo + * @return bool + * @throws LocalizedException + */ + public function save(FileInfo $fileInfo) + { + $parameters = []; + $parameters['initializationVector'] = $fileInfo->getInitializationVector(); + $parameters['path'] = $fileInfo->getPath(); + + $emptyParameters = array_diff($parameters, array_filter($parameters)); + if ($emptyParameters) { + throw new LocalizedException( + __('These arguments can\'t be empty "%1"', implode(', ', array_keys($emptyParameters))) + ); + } + + foreach ($this->encodedParameters as $encodedParameter) { + $parameters[$encodedParameter] = $this->encodeValue($parameters[$encodedParameter]); + } + + $this->flagManager->saveFlag($this->flagCode, $parameters); + + return true; + } + + /** + * Load FileInfo object. + * + * @return FileInfo + */ + public function load() + { + $parameters = $this->flagManager->getFlagData($this->flagCode) ?: []; + + $encodedParameters = array_intersect($this->encodedParameters, array_keys($parameters)); + foreach ($encodedParameters as $encodedParameter) { + $parameters[$encodedParameter] = $this->decodeValue($parameters[$encodedParameter]); + } + + $fileInfo = $this->fileInfoFactory->create($parameters); + + return $fileInfo; + } + + /** + * Encode value. + * + * @param string $value + * @return string + */ + private function encodeValue($value) + { + return base64_encode($value); + } + + /** + * Decode value. + * + * @param string $value + * @return string + */ + private function decodeValue($value) + { + return base64_decode($value); + } +} diff --git a/app/code/Magento/Analytics/Model/FileRecorder.php b/app/code/Magento/Analytics/Model/FileRecorder.php new file mode 100644 index 0000000000000000000000000000000000000000..70438a98d56f124fdbabb60ce6303c59db99c817 --- /dev/null +++ b/app/code/Magento/Analytics/Model/FileRecorder.php @@ -0,0 +1,136 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; + +/** + * Class for the handling of registration a new file for MBI. + */ +class FileRecorder +{ + /** + * Resource for managing FileInfo object. + * + * @var FileInfoManager + */ + private $fileInfoManager; + + /** + * @var FileInfoFactory + */ + private $fileInfoFactory; + + /** + * Subdirectory path for an encoded file. + * + * @var string + */ + private $fileSubdirectoryPath = 'analytics/'; + + /** + * File name of an encoded file. + * + * @var string + */ + private $encodedFileName = 'data.tgz'; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @param FileInfoManager $fileInfoManager + * @param FileInfoFactory $fileInfoFactory + * @param Filesystem $filesystem + */ + public function __construct( + FileInfoManager $fileInfoManager, + FileInfoFactory $fileInfoFactory, + Filesystem $filesystem + ) { + $this->fileInfoManager = $fileInfoManager; + $this->fileInfoFactory = $fileInfoFactory; + $this->filesystem = $filesystem; + } + + /** + * Save new encrypted file, register it and remove old registered file. + * + * @param EncodedContext $encodedContext + * @return bool + */ + public function recordNewFile(EncodedContext $encodedContext) + { + $directory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + + $fileRelativePath = $this->getFileRelativePath(); + $directory->writeFile($fileRelativePath, $encodedContext->getContent()); + + $fileInfo = $this->fileInfoManager->load(); + $this->registerFile($encodedContext, $fileRelativePath); + $this->removeOldFile($fileInfo, $directory); + + return true; + } + + /** + * Return relative path to encoded file. + * + * @return string + */ + private function getFileRelativePath() + { + return $this->fileSubdirectoryPath . hash('sha256', time()) + . '/' . $this->encodedFileName; + } + + /** + * Register encoded file. + * + * @param EncodedContext $encodedContext + * @param string $fileRelativePath + * @return bool + */ + private function registerFile(EncodedContext $encodedContext, $fileRelativePath) + { + $newFileInfo = $this->fileInfoFactory->create( + [ + 'path' => $fileRelativePath, + 'initializationVector' => $encodedContext->getInitializationVector(), + ] + ); + $this->fileInfoManager->save($newFileInfo); + + return true; + } + + /** + * Remove previously registered file. + * + * @param FileInfo $fileInfo + * @param WriteInterface $directory + * @return bool + */ + private function removeOldFile(FileInfo $fileInfo, WriteInterface $directory) + { + if (!$fileInfo->getPath()) { + return true; + } + + $directory->delete($fileInfo->getPath()); + + $directoryName = dirname($fileInfo->getPath()); + if ($directoryName !== '.') { + $directory->delete($directoryName); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/IntegrationManager.php b/app/code/Magento/Analytics/Model/IntegrationManager.php new file mode 100644 index 0000000000000000000000000000000000000000..61a40a955e026da2279fd78000439f8a4b79ff2e --- /dev/null +++ b/app/code/Magento/Analytics/Model/IntegrationManager.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Integration\Api\IntegrationServiceInterface; +use Magento\Config\Model\Config as SystemConfig; +use Magento\Integration\Model\Integration; +use Magento\Integration\Api\OauthServiceInterface; + +/** + * Class IntegrationManager + * + * Manages the integration user at magento side. + * User name stored in config. + * User roles + */ +class IntegrationManager +{ + /** + * @var SystemConfig + */ + private $config; + + /** + * @var IntegrationServiceInterface + */ + private $integrationService; + + /** + * @var OauthServiceInterface + */ + private $oauthService; + + /** + * IntegrationManager constructor + * + * @param SystemConfig $config + * @param IntegrationServiceInterface $integrationService + * @param OauthServiceInterface $oauthService + */ + public function __construct( + SystemConfig $config, + IntegrationServiceInterface $integrationService, + OauthServiceInterface $oauthService + ) { + $this->integrationService = $integrationService; + $this->config = $config; + $this->oauthService = $oauthService; + } + + /** + * Activate predefined integration user + * + * @return bool + * @throws NoSuchEntityException + */ + public function activateIntegration() + { + $integration = $this->integrationService->findByName( + $this->config->getConfigDataValue('analytics/integration_name') + ); + if (!$integration->getId()) { + throw new NoSuchEntityException(__('Cannot find predefined integration user!')); + } + $integrationData = $this->getIntegrationData(Integration::STATUS_ACTIVE); + $integrationData['integration_id'] = $integration->getId(); + $this->integrationService->update($integrationData); + return true; + } + + /** + * This method execute Generate Token command and enable integration + * + * @return bool|\Magento\Integration\Model\Oauth\Token + */ + public function generateToken() + { + $consumerId = $this->generateIntegration()->getConsumerId(); + $accessToken = $this->oauthService->getAccessToken($consumerId); + if (!$accessToken && $this->oauthService->createAccessToken($consumerId, true)) { + $accessToken = $this->oauthService->getAccessToken($consumerId); + } + return $accessToken; + } + + /** + * Returns consumer Id for MA integration user + * + * @return \Magento\Integration\Model\Integration + */ + private function generateIntegration() + { + $integration = $this->integrationService->findByName( + $this->config->getConfigDataValue('analytics/integration_name') + ); + if (!$integration->getId()) { + $integration = $this->integrationService->create($this->getIntegrationData()); + } + return $integration; + } + + /** + * Returns default attributes for MA integration user + * + * @param int $status + * @return array + */ + private function getIntegrationData($status = Integration::STATUS_INACTIVE) + { + $integrationData = [ + 'name' => $this->config->getConfigDataValue('analytics/integration_name'), + 'status' => $status, + 'all_resources' => false, + 'resource' => [ + 'Magento_Analytics::analytics', + 'Magento_Analytics::analytics_api' + ], + ]; + return $integrationData; + } +} diff --git a/app/code/Magento/Analytics/Model/Link.php b/app/code/Magento/Analytics/Model/Link.php new file mode 100644 index 0000000000000000000000000000000000000000..4a40796df4fd06ba507254d9143eea89f735ae4b --- /dev/null +++ b/app/code/Magento/Analytics/Model/Link.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Analytics\Api\Data\LinkInterface; + +/** + * Class Link + * + * Represents link with collected data and initialized vector for decryption. + */ +class Link implements LinkInterface +{ + /** + * @var string + */ + private $url; + + /** + * @var string + */ + private $initializationVector; + + /** + * Link constructor. + * + * @param string $url + * @param string $initializationVector + */ + public function __construct($url, $initializationVector) + { + $this->url = $url; + $this->initializationVector = $initializationVector; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @return string + */ + public function getInitializationVector() + { + return $this->initializationVector; + } +} diff --git a/app/code/Magento/Analytics/Model/LinkProvider.php b/app/code/Magento/Analytics/Model/LinkProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..2474653f4916c080d35653f05d5025dfe2bd93a9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/LinkProvider.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Analytics\Api\Data\LinkInterfaceFactory; +use Magento\Analytics\Api\LinkProviderInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Provides link to file with collected report data. + */ +class LinkProvider implements LinkProviderInterface +{ + /** + * @var LinkInterfaceFactory + */ + private $linkFactory; + + /** + * @var FileInfoManager + */ + private $fileInfoManager; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param LinkInterfaceFactory $linkInterfaceFactory + * @param FileInfoManager $fileInfoManager + * @param StoreManagerInterface $storeManager + */ + public function __construct( + LinkInterfaceFactory $linkFactory, + FileInfoManager $fileInfoManager, + StoreManagerInterface $storeManager + ) { + $this->linkFactory = $linkFactory; + $this->fileInfoManager = $fileInfoManager; + $this->storeManager = $storeManager; + } + + /** + * Returns base url to file according to store configuration + * + * @param FileInfo $fileInfo + * @return string + */ + private function getBaseUrl(FileInfo $fileInfo) + { + return $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) . $fileInfo->getPath(); + } + + /** + * Verify is requested file ready + * + * @param FileInfo $fileInfo + * @return bool + */ + private function isFileReady(FileInfo $fileInfo) + { + return $fileInfo->getPath() && $fileInfo->getInitializationVector(); + } + + /** + * @inheritdoc + */ + public function get() + { + $fileInfo = $this->fileInfoManager->load(); + if (!$this->isFileReady($fileInfo)) { + throw new NoSuchEntityException(__('File is not ready yet.')); + } + return $this->linkFactory->create( + [ + 'url' => $this->getBaseUrl($fileInfo), + 'initializationVector' => base64_encode($fileInfo->getInitializationVector()) + ] + ); + } +} diff --git a/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php b/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php new file mode 100644 index 0000000000000000000000000000000000000000..174272614fb19efe522768b172f0adbcb2f4ef15 --- /dev/null +++ b/app/code/Magento/Analytics/Model/Plugin/BaseUrlConfigPlugin.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Plugin; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Value; +use Magento\Store\Model\Store; + +/** + * Plugin on Base URL config value AfterSave method. + */ +class BaseUrlConfigPlugin +{ + /** + * @var SubscriptionUpdateHandler + */ + private $subscriptionUpdateHandler; + + /** + * @param SubscriptionUpdateHandler $subscriptionUpdateHandler + */ + public function __construct( + SubscriptionUpdateHandler $subscriptionUpdateHandler + ) { + $this->subscriptionUpdateHandler = $subscriptionUpdateHandler; + } + + /** + * Add additional handling after config value was saved. + * + * @param Value $subject + * @param Value $result + * @return Value + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAfterSave( + Value $subject, + Value $result + ) { + if ($this->isPluginApplicable($result)) { + $this->subscriptionUpdateHandler->processUrlUpdate($result->getOldValue()); + } + + return $result; + } + + /** + * @param Value $result + * @return bool + */ + private function isPluginApplicable(Value $result) + { + return $result->isValueChanged() + && ($result->getPath() === Store::XML_PATH_SECURE_BASE_URL) + && ($result->getScope() === ScopeConfigInterface::SCOPE_TYPE_DEFAULT); + } +} diff --git a/app/code/Magento/Analytics/Model/ProviderFactory.php b/app/code/Magento/Analytics/Model/ProviderFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..3a23430fca0771f692761f85d08b699477350d36 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ProviderFactory.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\ObjectManagerInterface; + +/** + * Class ProviderFactory + * + * Factory for report providers + */ +class ProviderFactory +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * @param string $providerName + * @return object + */ + public function create($providerName) + { + return $this->objectManager->get($providerName); + } +} diff --git a/app/code/Magento/Analytics/Model/ReportUrlProvider.php b/app/code/Magento/Analytics/Model/ReportUrlProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..e7fdf6f9e81323f31c9c215b5864d218cc82f2dc --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportUrlProvider.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector\OTPRequest; +use Magento\Analytics\Model\Exception\State\SubscriptionUpdateException; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; + +/** + * Provide URL on resource with reports. + */ +class ReportUrlProvider +{ + /** + * Resource for handling MBI token value. + * + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * Resource which provide OTP. + * + * @var OTPRequest + */ + private $otpRequest; + + /** + * @var ScopeConfigInterface + */ + private $config; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * Path to config value with URL which provide reports. + * + * @var string + */ + private $urlReportConfigPath = 'analytics/url/report'; + + /** + * @param AnalyticsToken $analyticsToken + * @param OTPRequest $otpRequest + * @param ScopeConfigInterface $config + * @param FlagManager $flagManager + */ + public function __construct( + AnalyticsToken $analyticsToken, + OTPRequest $otpRequest, + ScopeConfigInterface $config, + FlagManager $flagManager + ) { + $this->analyticsToken = $analyticsToken; + $this->otpRequest = $otpRequest; + $this->config = $config; + $this->flagManager = $flagManager; + } + + /** + * Provide URL on resource with reports. + * + * @return string + * @throws SubscriptionUpdateException + */ + public function getUrl() + { + if ($this->flagManager->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE)) { + throw new SubscriptionUpdateException(__( + 'Your Base URL has been changed and your reports are being updated. ' + . 'Advanced Reporting will be available once this change has been processed. Please try again later.' + )); + } + + $url = $this->config->getValue($this->urlReportConfigPath); + if ($this->analyticsToken->isTokenExist()) { + $otp = $this->otpRequest->call(); + if ($otp) { + $query = http_build_query(['otp' => $otp], '', '&'); + $url .= '?' . $query; + } + } + + return $url; + } +} diff --git a/app/code/Magento/Analytics/Model/ReportWriter.php b/app/code/Magento/Analytics/Model/ReportWriter.php new file mode 100644 index 0000000000000000000000000000000000000000..7128658947908a83fa5c1d917e19d51e75b5f3ef --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportWriter.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Analytics\ReportXml\DB\ReportValidator; +use Magento\Framework\Filesystem\Directory\WriteInterface; + +/** + * Writes reports in files in csv format + * @inheritdoc + */ +class ReportWriter implements ReportWriterInterface +{ + /** + * File name for error reporting file in archive + * + * @var string + */ + private $errorsFileName = 'errors.csv'; + + /** + * @var Config + */ + private $config; + + /** + * @var ProviderFactory + */ + private $providerFactory; + + /** + * @var ReportValidator + */ + private $reportValidator; + + /** + * ReportWriter constructor. + * + * @param ConfigInterface $config + * @param ReportValidator $reportValidator + * @param ProviderFactory $providerFactory + */ + public function __construct( + ConfigInterface $config, + ReportValidator $reportValidator, + ProviderFactory $providerFactory + ) { + $this->config = $config; + $this->reportValidator = $reportValidator; + $this->providerFactory = $providerFactory; + } + + /** + * {@inheritdoc} + */ + public function write(WriteInterface $directory, $path) + { + $errorsList = []; + foreach ($this->config->get() as $file) { + $provider = reset($file['providers']); + if (isset($provider['parameters']['name'])) { + $error = $this->reportValidator->validate($provider['parameters']['name']); + if ($error) { + $errorsList[] = $error; + continue; + } + } + /** @var $providerObject */ + $providerObject = $this->providerFactory->create($provider['class']); + $fileName = $provider['parameters'] ? $provider['parameters']['name'] : $provider['name']; + $fileFullPath = $path . $fileName . '.csv'; + $fileData = $providerObject->getReport(...array_values($provider['parameters'])); + $stream = $directory->openFile($fileFullPath, 'w+'); + $stream->lock(); + $headers = []; + foreach ($fileData as $row) { + if (!$headers) { + $headers = array_keys($row); + $stream->writeCsv($headers); + } + $stream->writeCsv($row); + } + $stream->unlock(); + $stream->close(); + } + if ($errorsList) { + $errorStream = $directory->openFile($path . $this->errorsFileName, 'w+'); + foreach ($errorsList as $error) { + $errorStream->lock(); + $errorStream->writeCsv($error); + $errorStream->unlock(); + } + $errorStream->close(); + } + + return true; + } +} diff --git a/app/code/Magento/Analytics/Model/ReportWriterInterface.php b/app/code/Magento/Analytics/Model/ReportWriterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a611095a47ae43cbf2fba734401bc7de7a928c14 --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportWriterInterface.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Framework\Filesystem\Directory\WriteInterface; + +/** + * Interface ReportWriterInterface + * + * Writes report files + * Executes export of collected data + * Iterates registered providers @see etc/analytics.xml + * Collects data (to TMP folder) + */ +interface ReportWriterInterface +{ + /** + * Writes report files to provided path + * + * @param WriteInterface $directory + * @param string $path + * @return void + */ + public function write(WriteInterface $directory, $path); +} diff --git a/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php b/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..a0800387d28f5c4d6d101fd79b87351b65d2113f --- /dev/null +++ b/app/code/Magento/Analytics/Model/ReportXml/ModuleIterator.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\ReportXml; + +use Magento\Framework\Module\Manager as ModuleManager; + +/** + * Class ModuleIterator + */ +class ModuleIterator extends \IteratorIterator +{ + /** + * @var ModuleManager + */ + private $moduleManager; + + /** + * ModuleIterator constructor. + * + * @param ModuleManager $moduleManager + * @param \Traversable $iterator + */ + public function __construct( + ModuleManager $moduleManager, + \Traversable $iterator + ) { + parent::__construct($iterator); + $this->moduleManager = $moduleManager; + } + + /** + * Returns module with module status + * + * @return array + */ + public function current() + { + $current = parent::current(); + if (is_array($current) && isset($current['module_name'])) { + $current['status'] = + $this->moduleManager->isEnabled($current['module_name']) == 1 ? 'Enabled' : "Disabled"; + } + return $current; + } +} diff --git a/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php b/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..0d226a9de7dc2147570075637342adaeea955ae9 --- /dev/null +++ b/app/code/Magento/Analytics/Model/StoreConfigurationProvider.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class StoreConfigurationProvider + * Provides config data report + */ +class StoreConfigurationProvider +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var string[] + */ + private $configPaths; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param StoreManagerInterface $storeManager + * @param string[] $configPaths + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + StoreManagerInterface $storeManager, + array $configPaths + ) { + $this->scopeConfig = $scopeConfig; + $this->configPaths = $configPaths; + $this->storeManager = $storeManager; + } + + /** + * Generates report using config paths from di.xml + * For each website and store + * @return \IteratorIterator + */ + public function getReport() + { + $configReport = $this->generateReportForScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0); + + /** @var WebsiteInterface $website */ + foreach ($this->storeManager->getWebsites() as $website) { + $configReport = array_merge( + $this->generateReportForScope(ScopeInterface::SCOPE_WEBSITES, $website->getId()), + $configReport + ); + } + + /** @var StoreInterface $store */ + foreach ($this->storeManager->getStores() as $store) { + $configReport = array_merge( + $this->generateReportForScope(ScopeInterface::SCOPE_STORES, $store->getId()), + $configReport + ); + } + return new \IteratorIterator(new \ArrayIterator($configReport)); + } + + /** + * Creates report from config for scope type and scope id. + * + * @param string $scope + * @param int $scopeId + * @return array + */ + private function generateReportForScope($scope, $scopeId) + { + $report = []; + foreach ($this->configPaths as $configPath) { + $report[] = [ + "config_path" => $configPath, + "scope" => $scope, + "scope_id" => $scopeId, + "value" => $this->scopeConfig->getValue( + $configPath, + $scope, + $scopeId + ) + ]; + } + return $report; + } +} diff --git a/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php b/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..1dd831a672faa20b865d8f599f1b73174e3619ec --- /dev/null +++ b/app/code/Magento/Analytics/Model/SubscriptionStatusProvider.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; + +/** + * Provider of subscription status. + */ +class SubscriptionStatusProvider +{ + /** + * Represents an enabled subscription state. + */ + const ENABLED = "Enabled"; + + /** + * Represents a failed subscription state. + */ + const FAILED = "Failed"; + + /** + * Represents a pending subscription state. + */ + const PENDING = "Pending"; + + /** + * Represents a disabled subscription state. + */ + const DISABLED = "Disabled"; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var AnalyticsToken + */ + private $analyticsToken; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param AnalyticsToken $analyticsToken + * @param FlagManager $flagManager + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + AnalyticsToken $analyticsToken, + FlagManager $flagManager + ) { + $this->scopeConfig = $scopeConfig; + $this->analyticsToken = $analyticsToken; + $this->flagManager = $flagManager; + } + + /** + * Retrieve subscription status to Magento BI Advanced Reporting. + * + * Statuses: + * Enabled - if subscription is enabled and MA token was received; + * Pending - if subscription is enabled and MA token was not received; + * Disabled - if subscription is not enabled. + * Failed - if subscription is enabled and token was not received after attempts ended. + * + * @return string + */ + public function getStatus() + { + $isSubscriptionEnabledInConfig = $this->scopeConfig->getValue('analytics/subscription/enabled'); + if ($isSubscriptionEnabledInConfig) { + return $this->getStatusForEnabledSubscription(); + } + + return $this->getStatusForDisabledSubscription(); + } + + /** + * Retrieve status for subscription that enabled in config. + * + * @return string + */ + public function getStatusForEnabledSubscription() + { + $status = static::ENABLED; + if ($this->flagManager->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE)) { + $status = self::PENDING; + } + + if (!$this->analyticsToken->isTokenExist()) { + $status = static::PENDING; + if ($this->flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) === null) { + $status = static::FAILED; + } + } + + return $status; + } + + /** + * Retrieve status for subscription that disabled in config. + * + * @return string + */ + public function getStatusForDisabledSubscription() + { + return static::DISABLED; + } +} diff --git a/app/code/Magento/Analytics/Model/System/Message/NotificationAboutFailedSubscription.php b/app/code/Magento/Analytics/Model/System/Message/NotificationAboutFailedSubscription.php new file mode 100644 index 0000000000000000000000000000000000000000..9aaa2ebb3b56f926a383781dc6e591ca1634085a --- /dev/null +++ b/app/code/Magento/Analytics/Model/System/Message/NotificationAboutFailedSubscription.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\System\Message; + +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Framework\Notification\MessageInterface; +use Magento\Framework\UrlInterface; + +/** + * Represents an analytics notification about failed subscription. + */ +class NotificationAboutFailedSubscription implements MessageInterface +{ + /** + * @var SubscriptionStatusProvider + */ + private $subscriptionStatusProvider; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @param SubscriptionStatusProvider $subscriptionStatusProvider + * @param UrlInterface $urlBuilder + */ + public function __construct(SubscriptionStatusProvider $subscriptionStatusProvider, UrlInterface $urlBuilder) + { + $this->subscriptionStatusProvider = $subscriptionStatusProvider; + $this->urlBuilder = $urlBuilder; + } + + /** + * @inheritdoc + * + * @codeCoverageIgnore + */ + public function getIdentity() + { + return hash('sha256', 'ANALYTICS_NOTIFICATION'); + } + + /** + * {@inheritdoc} + */ + public function isDisplayed() + { + return $this->subscriptionStatusProvider->getStatus() === SubscriptionStatusProvider::FAILED; + } + + /** + * {@inheritdoc} + */ + public function getText() + { + $messageDetails = ''; + + $messageDetails .= __('Failed to synchronize data to the Magento Business Intelligence service. '); + $messageDetails .= __( + '<a href="%1">Retry Synchronization</a>', + $this->urlBuilder->getUrl('analytics/subscription/retry') + ); + + return $messageDetails; + } + + /** + * @inheritdoc + * + * @codeCoverageIgnore + */ + public function getSeverity() + { + return self::SEVERITY_MAJOR; + } +} diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7ec64abcd9b86a09fa642f82966f74e3a5c85625 --- /dev/null +++ b/app/code/Magento/Analytics/README.md @@ -0,0 +1,41 @@ +# Magento_Analytics Module + +The Magento_Analytics module integrates your Magento instance with the [Magento Business Intelligence (MBI)](https://magento.com/products/business-intelligence) to use [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html) functionality. + +The module implements the following functionality: + +* enabling subscription to the MBI and automatic re-subscription +* changing the base URL with the same MBI account remained +* declaring the configuration schemas for report data collection +* collecting the Magento instance data as reports for the MBI +* introducing API that provides the collected data +* extending Magento configuration with the module parameters: + * subscription status (enabled/disabled) + * industry (a business area in which the instance website works) + * time of data collection (time of the day when the module collects data) + +## Structure + +Beyond the [usual module file structure](http://devdocs.magento.com/guides/v2.2/architecture/archi_perspectives/components/modules/mod_intro.html) the module contains a directory `ReportXml`. +[Report XML](http://devdocs.magento.com/guides/v2.2/advanced-reporting/report-xml.html) is a markup language used to build reports for Advanced Reporting. +The language declares SQL queries using XML declaration. + +## Subscription Process + +The subscription to the MBI service is enabled during the installation process of the Analytics module. Each administrator will be notified of these new features upon their initial login to the Admin Panel. + +## Analytics Settings + +Configuration settings for the Analytics module can be modified in the Admin Panel on the Stores > Configuration page under the General > Advanced Reporting tab. + +The following options can be adjusted: +* Advanced Reporting Service (Enabled/Disabled) + * Alters the status of the Advanced Reporting subscription +* Time of day to send data (Hour/Minute/Second in the store's time zone) + * Defines when the data collection process for the Advanced Reporting service occurs +* Industry + * Defines the industry of the store in order to create a personalized Advanced Reporting experience + +## Extensibility + +We do not recommend to extend the Magento_Analytics module. It introduces an API that is purposed to transfer the collected data. Note that the API cannot be used for other needs. diff --git a/app/code/Magento/Analytics/ReportXml/Config.php b/app/code/Magento/Analytics/ReportXml/Config.php new file mode 100644 index 0000000000000000000000000000000000000000..f50dcf941bf5069291dce637573772877937462f --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml; + +use Magento\Framework\Config\DataInterface; + +/** + * Class Config + * + * Config of ReportXml + */ +class Config implements ConfigInterface +{ + /** + * @var DataInterface + */ + private $data; + + /** + * Config constructor. + * + * @param DataInterface $data + */ + public function __construct( + DataInterface $data + ) { + $this->data = $data; + } + + /** + * Returns config value by name + * + * @param string $queryName + * @return array + */ + public function get($queryName) + { + return $this->data->get($queryName); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php b/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php new file mode 100644 index 0000000000000000000000000000000000000000..9e0b20a6ad414e5b7d60ce073b58a38ec0fccc03 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Converter/Xml.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\Config\Converter; + +use Magento\Framework\Config\ConverterInterface; + +/** + * A converter of reports configuration. + * + * Converts configuration data stored in XML format into corresponding PHP array. + */ +class Xml implements ConverterInterface +{ + /** + * Converts XML node into corresponding array. + * + * @param \DOMNode $source + * @return array|string + */ + private function convertNode(\DOMNode $source) + { + $result = []; + if ($source->hasAttributes()) { + $attrs = $source->attributes; + foreach ($attrs as $attr) { + $result[$attr->name] = $attr->value; + } + } + if ($source->hasChildNodes()) { + $children = $source->childNodes; + if ($children->length == 1) { + $child = $children->item(0); + if ($child->nodeType == XML_TEXT_NODE) { + $result['_value'] = $child->nodeValue; + return count($result) == 1 ? $result['_value'] : $result; + } + } + foreach ($children as $child) { + if ($child instanceof \DOMCharacterData) { + continue; + } + $result[$child->nodeName][] = $this->convertNode($child); + } + } + return $result; + } + + /** + * Converts XML document into corresponding array. + * + * @param \DOMDocument $source + * @return array + */ + public function convert($source) + { + return $this->convertNode($source); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Config/Mapper.php b/app/code/Magento/Analytics/ReportXml/Config/Mapper.php new file mode 100644 index 0000000000000000000000000000000000000000..4dda8f3c733a6bdbbe4b1b45a594255623b18bb8 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Mapper.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\Config; + +/** + * A reports configuration mapper. + * + * Transforms configuration data to improve its usability. + * + * @see usage examples in \Magento\Analytics\ReportXml\Config\Reader + */ +class Mapper +{ + /** + * Transforms configuration data. + * + * @param array $configData + * @return array + */ + public function execute($configData) + { + if (!isset($configData['config'][0]['report'])) { + return []; + } + + $queries = []; + foreach ($configData['config'][0]['report'] as $queryData) { + $entityData = array_shift($queryData['source']); + $queries[$queryData['name']] = $queryData; + $queries[$queryData['name']]['source'] = $entityData; + } + return $queries; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Config/Reader.php b/app/code/Magento/Analytics/ReportXml/Config/Reader.php new file mode 100644 index 0000000000000000000000000000000000000000..3e0bc3cb5c79641faf053e7195f6b6003e715593 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Config/Reader.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\Config; + +use Magento\Framework\Config\ReaderInterface; + +/** + * A composite reader of reports configuration. + * + * Reads configuration data using declared readers. + */ +class Reader implements ReaderInterface +{ + /** + * A list of declared readers. + * + * The list may be configured in each module via '/etc/di.xml'. + * + * @var ReaderInterface[] + */ + private $readers; + + /** + * @var Mapper + */ + private $mapper; + + /** + * @param Mapper $mapper + * @param array $readers + */ + public function __construct( + Mapper $mapper, + $readers = [] + ) { + $this->readers = $readers; + $this->mapper = $mapper; + } + + /** + * Reads configuration according to the given scope. + * + * @param string|null $scope + * @return array + */ + public function read($scope = null) + { + $data = []; + foreach ($this->readers as $reader) { + $data = array_merge_recursive($data, $reader->read($scope)); + } + return $this->mapper->execute($data); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/ConfigInterface.php b/app/code/Magento/Analytics/ReportXml/ConfigInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ec03ddf429c063a5dda0858961ed2d2fdfa53c09 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ConfigInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml; + +/** + * Interface ConfigInterface + * + * Interface for ReportXml Config + */ +interface ConfigInterface +{ + /** + * Config of ReportXml + * + * @param string $queryName + * @return array + */ + public function get($queryName); +} diff --git a/app/code/Magento/Analytics/ReportXml/ConnectionFactory.php b/app/code/Magento/Analytics/ReportXml/ConnectionFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..e591ed265965107c87386c497be1c02aa71a2763 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ConnectionFactory.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; + +/** + * Class ConnectionFactory + * + * Creates connection instance for export according to existing one + * This connection does not use buffered statement, also this connection is not persistent + */ +class ConnectionFactory +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * ConnectionFactory constructor. + * + * @param ResourceConnection $resourceConnection + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ResourceConnection $resourceConnection, + ObjectManagerInterface $objectManager + ) { + $this->resourceConnection = $resourceConnection; + $this->objectManager = $objectManager; + } + + /** + * Creates one-time connection for export + * + * @param string $connectionName + * @return AdapterInterface + */ + public function getConnection($connectionName) + { + $connection = $this->resourceConnection->getConnection($connectionName); + $connectionClassName = get_class($connection); + $configData = $connection->getConfig(); + $configData['use_buffered_query'] = false; + unset($configData['persistent']); + return $this->objectManager->create( + $connectionClassName, + [ + 'config' => $configData + ] + ); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..083b4843c185aaf7726b987581c14f0366987604 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/AssemblerInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\DB\Assembler; + +use Magento\Analytics\ReportXml\DB\SelectBuilder; + +/** + * Interface AssemblerInterface + * + * Introduces family of SQL assemblers + * Each assembler populates SelectBuilder with config information + * @see usage examples at \Magento\Analytics\ReportXml\QueryFactory + */ +interface AssemblerInterface +{ + /** + * Assemble SQL statement + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig); +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/FilterAssembler.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FilterAssembler.php new file mode 100644 index 0000000000000000000000000000000000000000..251c43f5e1b718d9dbb45c3143240cbb9837710f --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FilterAssembler.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml\DB\Assembler; + +use Magento\Analytics\ReportXml\DB\NameResolver; +use Magento\Analytics\ReportXml\DB\SelectBuilder; +use Magento\Analytics\ReportXml\DB\ConditionResolver; + +/** + * Class FilterAssembler + * + * Assembles WHERE conditions + */ +class FilterAssembler implements AssemblerInterface +{ + /** + * @var ConditionResolver + */ + private $conditionResolver; + + /** + * @var NameResolver + */ + private $nameResolver; + + /** + * FilterAssembler constructor. + * + * @param ConditionResolver $conditionResolver + * @param NameResolver $nameResolver + */ + public function __construct( + ConditionResolver $conditionResolver, + NameResolver $nameResolver + ) { + $this->conditionResolver = $conditionResolver; + $this->nameResolver = $nameResolver; + } + + /** + * Assembles WHERE conditions + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig) + { + if (!isset($queryConfig['source']['filter'])) { + return $selectBuilder; + } + $filters = $this->conditionResolver->getFilter( + $selectBuilder, + $queryConfig['source']['filter'], + $this->nameResolver->getAlias($queryConfig['source']) + ); + $selectBuilder->setFilters(array_merge_recursive($selectBuilder->getFilters(), [$filters])); + return $selectBuilder; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php new file mode 100644 index 0000000000000000000000000000000000000000..811119ace221b6e9aeb6d8ba40ecc9287a7ec17c --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/FromAssembler.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml\DB\Assembler; + +use Magento\Analytics\ReportXml\DB\ColumnsResolver; +use Magento\Analytics\ReportXml\DB\SelectBuilder; +use Magento\Analytics\ReportXml\DB\NameResolver; +use Magento\Framework\App\ResourceConnection; + +/** + * Assembles FROM condition + */ +class FromAssembler implements AssemblerInterface +{ + /** + * @var NameResolver + */ + private $nameResolver; + + /** + * @var ColumnsResolver + */ + private $columnsResolver; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param NameResolver $nameResolver + * @param ColumnsResolver $columnsResolver + * @param ResourceConnection $resourceConnection + */ + public function __construct( + NameResolver $nameResolver, + ColumnsResolver $columnsResolver, + ResourceConnection $resourceConnection + ) { + $this->nameResolver = $nameResolver; + $this->columnsResolver = $columnsResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Assembles FROM condition + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig) + { + $selectBuilder->setFrom( + [ + $this->nameResolver->getAlias($queryConfig['source']) => + $this->resourceConnection + ->getTableName($this->nameResolver->getName($queryConfig['source'])), + ] + ); + $columns = $this->columnsResolver->getColumns($selectBuilder, $queryConfig['source']); + $selectBuilder->setColumns(array_merge($selectBuilder->getColumns(), $columns)); + return $selectBuilder; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php b/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php new file mode 100644 index 0000000000000000000000000000000000000000..f3c6540a25171815e779f6687b31c07633ea1554 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/Assembler/JoinAssembler.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml\DB\Assembler; + +use Magento\Analytics\ReportXml\DB\NameResolver; +use Magento\Analytics\ReportXml\DB\SelectBuilder; +use Magento\Analytics\ReportXml\DB\ConditionResolver; +use Magento\Analytics\ReportXml\DB\ColumnsResolver; +use Magento\Framework\App\ResourceConnection; + +/** + * Class JoinAssembler + * + * Assembles JOIN conditions + */ +class JoinAssembler implements AssemblerInterface +{ + /** + * @var ConditionResolver + */ + private $conditionResolver; + + /** + * @var NameResolver + */ + private $nameResolver; + + /** + * @var ColumnsResolver + */ + private $columnsResolver; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param ConditionResolver $conditionResolver + * @param ColumnsResolver $columnsResolver + * @param NameResolver $nameResolver + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ConditionResolver $conditionResolver, + ColumnsResolver $columnsResolver, + NameResolver $nameResolver, + ResourceConnection $resourceConnection + ) { + $this->conditionResolver = $conditionResolver; + $this->nameResolver = $nameResolver; + $this->columnsResolver = $columnsResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Assembles JOIN conditions + * + * @param SelectBuilder $selectBuilder + * @param array $queryConfig + * @return SelectBuilder + */ + public function assemble(SelectBuilder $selectBuilder, $queryConfig) + { + if (!isset($queryConfig['source']['link-source'])) { + return $selectBuilder; + } + $joins = []; + $filters = $selectBuilder->getFilters(); + + $sourceAlias = $this->nameResolver->getAlias($queryConfig['source']); + + foreach ($queryConfig['source']['link-source'] as $join) { + $joinAlias = $this->nameResolver->getAlias($join); + + $joins[$joinAlias] = [ + 'link-type' => isset($join['link-type']) ? $join['link-type'] : 'left', + 'table' => [ + $joinAlias => $this->resourceConnection + ->getTableName($this->nameResolver->getName($join)), + ], + 'condition' => $this->conditionResolver->getFilter( + $selectBuilder, + $join['using'], + $joinAlias, + $sourceAlias + ) + ]; + if (isset($join['filter'])) { + $filters = array_merge( + $filters, + [ + $this->conditionResolver->getFilter( + $selectBuilder, + $join['filter'], + $joinAlias, + $sourceAlias + ) + ] + ); + } + $columns = $this->columnsResolver->getColumns($selectBuilder, isset($join['attribute']) ? $join : []); + $selectBuilder->setColumns(array_merge($selectBuilder->getColumns(), $columns)); + } + $selectBuilder->setFilters($filters); + $selectBuilder->setJoins(array_merge($selectBuilder->getJoins(), $joins)); + return $selectBuilder; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..e6474d4c5dc6d5045e12980f8bc69574d321c8a3 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ColumnsResolver.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml\DB; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Sql\ColumnValueExpression; + +/** + * Class ColumnsResolver + * + * Resolves columns names + */ +class ColumnsResolver +{ + /** + * @var NameResolver + */ + private $nameResolver; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * ColumnsResolver constructor. + * + * @param NameResolver $nameResolver + * @param ResourceConnection $resourceConnection + */ + public function __construct( + NameResolver $nameResolver, + ResourceConnection $resourceConnection + ) { + $this->nameResolver = $nameResolver; + $this->resourceConnection = $resourceConnection; + } + + /** + * Returns connection + * + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (!$this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + return $this->connection; + } + + /** + * Set columns list to SelectBuilder + * + * @param SelectBuilder $selectBuilder + * @param array $entityConfig + * @return array + */ + public function getColumns(SelectBuilder $selectBuilder, $entityConfig) + { + if (!isset($entityConfig['attribute'])) { + return []; + } + $group = []; + $columns = $selectBuilder->getColumns(); + foreach ($entityConfig['attribute'] as $attributeData) { + $columnAlias = $this->nameResolver->getAlias($attributeData); + $tableAlias = $this->nameResolver->getAlias($entityConfig); + $columnName = $this->nameResolver->getName($attributeData); + if (isset($attributeData['function'])) { + $prefix = ''; + if (isset($attributeData['distinct']) && $attributeData['distinct'] == true) { + $prefix = ' DISTINCT '; + } + $expression = new ColumnValueExpression( + strtoupper($attributeData['function']) . '(' . $prefix + . $this->getConnection()->quoteIdentifier($tableAlias . '.' . $columnName) + . ')' + ); + } else { + $expression = $tableAlias . '.' . $columnName; + } + $columns[$columnAlias] = $expression; + if (isset($attributeData['group'])) { + $group[$columnAlias] = $expression; + } + } + $selectBuilder->setGroup(array_merge($selectBuilder->getGroup(), $group)); + return $columns; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php b/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..773b96959e7948e695eb66a240172de226af1f90 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ConditionResolver.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml\DB; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Sql\Expression; + +/** + * Class ConditionResolver + * + * Mapper for WHERE conditions + */ +class ConditionResolver +{ + /** + * @var array + */ + private $conditionMap = [ + 'eq' => '%1$s = %2$s', + 'neq' => '%1$s != %2$s', + 'like' => '%1$s LIKE %2$s', + 'nlike' => '%1$s NOT LIKE %2$s', + 'in' => '%1$s IN(%2$s)', + 'nin' => '%1$s NOT IN(%2$s)', + 'notnull' => '%1$s IS NOT NULL', + 'null' => '%1$s IS NULL', + 'gt' => '%1$s > %2$s', + 'lt' => '%1$s < %2$s', + 'gteq' => '%1$s >= %2$s', + 'lteq' => '%1$s <= %2$s', + 'finset' => 'FIND_IN_SET(%2$s, %1$s)' + ]; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * ConditionResolver constructor. + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ResourceConnection $resourceConnection + ) { + $this->resourceConnection = $resourceConnection; + } + + /** + * Returns connection + * + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (!$this->connection) { + $this->connection = $this->resourceConnection->getConnection(); + } + return $this->connection; + } + + /** + * Returns value for condition + * + * @param string $condition + * @param string $referencedEntity + * @return mixed|null|string|\Zend_Db_Expr + */ + private function getValue($condition, $referencedEntity) + { + $value = null; + $argument = isset($condition['_value']) ? $condition['_value'] : null; + if (!isset($condition['type'])) { + $condition['type'] = 'value'; + } + + switch ($condition['type']) { + case "value": + $value = $this->getConnection()->quote($argument); + break; + case "variable": + $value = new Expression($argument); + break; + case "identifier": + $value = $this->getConnection()->quoteIdentifier( + $referencedEntity ? $referencedEntity . '.' . $argument : $argument + ); + break; + } + return $value; + } + + /** + * Returns condition for WHERE + * + * @param SelectBuilder $selectBuilder + * @param string $tableName + * @param array $condition + * @param null|string $referencedEntity + * @return string + */ + private function getCondition(SelectBuilder $selectBuilder, $tableName, $condition, $referencedEntity = null) + { + $columns = $selectBuilder->getColumns(); + if (isset($columns[$condition['attribute']]) + && $columns[$condition['attribute']] instanceof Expression + ) { + $expression = $columns[$condition['attribute']]; + } else { + $expression = $this->getConnection()->quoteIdentifier($tableName . '.' . $condition['attribute']); + } + return sprintf( + $this->conditionMap[$condition['operator']], + $expression, + $this->getValue($condition, $referencedEntity) + ); + } + + /** + * Build WHERE condition + * + * @param SelectBuilder $selectBuilder + * @param array $filterConfig + * @param string $aliasName + * @param null|string $referencedAlias + * @return array + */ + public function getFilter(SelectBuilder $selectBuilder, $filterConfig, $aliasName, $referencedAlias = null) + { + $filtersParts = []; + foreach ($filterConfig as $filter) { + $glue = $filter['glue']; + $parts = []; + foreach ($filter['condition'] as $condition) { + if (isset($condition['type']) && $condition['type'] == 'variable') { + $selectBuilder->setParams(array_merge($selectBuilder->getParams(), [$condition['_value']])); + } + $parts[] = $this->getCondition( + $selectBuilder, + $aliasName, + $condition, + $referencedAlias + ); + } + if (isset($filter['filter'])) { + $parts[] = '(' . $this->getFilter( + $selectBuilder, + $filter['filter'], + $aliasName, + $referencedAlias + ) . ')'; + } + $filtersParts[] = '(' . implode(' ' . strtoupper($glue) . ' ', $parts) . ')'; + } + return implode(' OR ', $filtersParts); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php b/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..c9543002eb272da88ca14dedbb2648c6e5bd116f --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/NameResolver.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\DB; + +/** + * Class NameResolver + * + * Resolver for source names + */ +class NameResolver +{ + /** + * Returns element for name + * + * @param array $elementConfig + * @return string + */ + public function getName($elementConfig) + { + return $elementConfig['name']; + } + + /** + * Returns alias + * + * @param array $elementConfig + * @return string + */ + public function getAlias($elementConfig) + { + $alias = $this->getName($elementConfig); + if (isset($elementConfig['alias'])) { + $alias = $elementConfig['alias']; + } + return $alias; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php b/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..21a641f0a71c7cb5320d5a4c24d5dd7f8cc1b8b0 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/ReportValidator.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\DB; + +use Magento\Analytics\ReportXml\ConnectionFactory; +use Magento\Analytics\ReportXml\QueryFactory; +use Magento\Framework\Api\SearchCriteriaInterface; + +/** + * Class ReportValidator + * + * Validates report definitions by doing query to storage with limit 0 + */ +class ReportValidator +{ + /** + * @var ConnectionFactory + */ + private $connectionFactory; + + /** + * @var QueryFactory + */ + private $queryFactory; + + /** + * ReportValidator constructor. + * + * Needs connection and query factory for do a query + * + * @param ConnectionFactory $connectionFactory + * @param QueryFactory $queryFactory + */ + public function __construct(ConnectionFactory $connectionFactory, QueryFactory $queryFactory) + { + $this->connectionFactory = $connectionFactory; + $this->queryFactory = $queryFactory; + } + + /** + * Tries to do query for provided report with limit 0 and return error information if it failed + * + * @param string $name + * @param SearchCriteriaInterface $criteria + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function validate($name, SearchCriteriaInterface $criteria = null) + { + $query = $this->queryFactory->create($name); + $connection = $this->connectionFactory->getConnection($query->getConnectionName()); + $query->getSelect()->limit(0); + try { + $connection->query($query->getSelect()); + } catch (\Zend_Db_Statement_Exception $e) { + return [$name, $e->getMessage()]; + } + + return []; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..4e5a1940773b1c893ab44a29d77102486afca8e7 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilder.php @@ -0,0 +1,289 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml\DB; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; + +/** + * Class SelectBuilder + * + * Responsible for Select object creation, works as a builder. Returns Select as result; + * Used in SQL assemblers. + */ +class SelectBuilder +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var string + */ + private $connectionName; + + /** + * @var array + */ + private $from; + + /** + * @var array + */ + private $group = []; + + /** + * @var array + */ + private $columns = []; + + /** + * @var array + */ + private $filters = []; + + /** + * @var array + */ + private $joins = []; + + /** + * @var array + */ + private $params = []; + + /** + * @var array + */ + private $having = []; + + /** + * SelectBuilder constructor. + * + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ResourceConnection $resourceConnection + ) { + $this->resourceConnection = $resourceConnection; + } + + /** + * Get join condition + * + * @return array + */ + public function getJoins() + { + return $this->joins; + } + + /** + * Set joins conditions + * + * @param array $joins + * @return void + */ + public function setJoins($joins) + { + $this->joins = $joins; + } + + /** + * Get connection name + * + * @return string + */ + public function getConnectionName() + { + return $this->connectionName; + } + + /** + * Set connection name + * + * @param string $connectionName + * @return void + */ + public function setConnectionName($connectionName) + { + $this->connectionName = $connectionName; + } + + /** + * Get columns + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Set columns + * + * @param array $columns + * @return void + */ + public function setColumns($columns) + { + $this->columns = $columns; + } + + /** + * Get filters + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Set filters + * + * @param array $filters + * @return void + */ + public function setFilters($filters) + { + $this->filters = $filters; + } + + /** + * Get from condition + * + * @return array + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set from condition + * + * @param array $from + * @return void + */ + public function setFrom($from) + { + $this->from = $from; + } + + /** + * Process JOIN conditions + * + * @param Select $select + * @param array $joinConfig + * @return Select + */ + private function processJoin(Select $select, $joinConfig) + { + switch ($joinConfig['link-type']) { + case 'left': + $select->joinLeft($joinConfig['table'], $joinConfig['condition'], []); + break; + case 'inner': + $select->joinInner($joinConfig['table'], $joinConfig['condition'], []); + break; + case 'right': + $select->joinRight($joinConfig['table'], $joinConfig['condition'], []); + break; + } + return $select; + } + + /** + * Creates Select object + * + * @return Select + */ + public function create() + { + $connection = $this->resourceConnection->getConnection($this->getConnectionName()); + $select = $connection->select(); + $select->from($this->getFrom(), []); + $select->columns($this->getColumns()); + foreach ($this->getFilters() as $filter) { + $select->where($filter); + } + foreach ($this->getJoins() as $joinConfig) { + $select = $this->processJoin($select, $joinConfig); + } + if (!empty($this->getGroup())) { + $select->group(implode(', ', $this->getGroup())); + } + return $select; + } + + /** + * Returns group + * + * @return array + */ + public function getGroup() + { + return $this->group; + } + + /** + * Set group + * + * @param array $group + * @return void + */ + public function setGroup($group) + { + $this->group = $group; + } + + /** + * Get parameters + * + * @return array + */ + public function getParams() + { + return $this->params; + } + + /** + * Set parameters + * + * @param array $params + * @return void + */ + public function setParams($params) + { + $this->params = $params; + } + + /** + * Get having condition + * + * @return array + */ + public function getHaving() + { + return $this->having; + } + + /** + * Set having condition + * + * @param array $having + * @return void + */ + public function setHaving($having) + { + $this->having = $having; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..1d88d4618efc5accad10a39e334c2be553de0483 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/DB/SelectBuilderFactory.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml\DB; + +use Magento\Framework\ObjectManagerInterface; + +/** + * Factory class for @see \Magento\Analytics\ReportXml\DB\SelectBuilder + */ +class SelectBuilderFactory +{ + /** + * Object Manager instance + * + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * SelectBuilderFactory constructor. + * + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * Create class instance with specified parameters + * + * @param array $data + * @return SelectBuilder + */ + public function create(array $data = []) + { + return $this->objectManager->create(SelectBuilder::class, $data); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/IteratorFactory.php b/app/code/Magento/Analytics/ReportXml/IteratorFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..a196cef8b66dc7cbebf8f15721e35836710e10cd --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/IteratorFactory.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml; + +use Magento\Framework\ObjectManagerInterface; + +/** + * Class IteratorFactory + */ +class IteratorFactory +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var string + */ + private $defaultIteratorName; + + /** + * IteratorFactory constructor. + * + * @param ObjectManagerInterface $objectManager + * @param string $defaultIteratorName + */ + public function __construct( + ObjectManagerInterface $objectManager, + $defaultIteratorName = \IteratorIterator::class + ) { + $this->objectManager = $objectManager; + $this->defaultIteratorName = $defaultIteratorName; + } + + /** + * Creates instance of the result iterator with the query result as an input + * Result iterator can be changed through report configuration + * <report name="reportName" iterator="Iterator\Class\Name"> + * < ... + * </report> + * Uses IteratorIterator by default + * + * @param \Traversable $result + * @param string|null $iteratorName + * @return \IteratorIterator + */ + public function create(\Traversable $result, $iteratorName = null) + { + return $this->objectManager->create( + $iteratorName ?: $this->defaultIteratorName, + [ + 'iterator' => $result + ] + ); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/Query.php b/app/code/Magento/Analytics/ReportXml/Query.php new file mode 100644 index 0000000000000000000000000000000000000000..46ec2fb494183d7e979879284cf0c0c7e87337cf --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/Query.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml; + +use Magento\Framework\DB\Select; + +/** + * Class Query + * + * Query object, contains SQL statement, information about connection, query arguments + */ +class Query implements \JsonSerializable +{ + /** + * @var Select + */ + private $select; + + /** + * @var \Magento\Analytics\ReportXml\SelectHydrator + */ + private $selectHydrator; + + /** + * @var string + */ + private $connectionName; + + /** + * @var array + */ + private $config; + + /** + * Query constructor. + * + * @param Select $select + * @param SelectHydrator $selectHydrator + * @param string $connectionName + * @param array $config + */ + public function __construct( + Select $select, + SelectHydrator $selectHydrator, + $connectionName, + $config + ) { + $this->select = $select; + $this->connectionName = $connectionName; + $this->selectHydrator = $selectHydrator; + $this->config = $config; + } + + /** + * @return Select + */ + public function getSelect() + { + return $this->select; + } + + /** + * @return string + */ + public function getConnectionName() + { + return $this->connectionName; + } + + /** + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Specify data which should be serialized to JSON + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @return mixed data which can be serialized by <b>json_encode</b>, + * which is a value of any type other than a resource. + * @since 5.4.0 + */ + public function jsonSerialize() + { + return [ + 'connectionName' => $this->getConnectionName(), + 'select_parts' => $this->selectHydrator->extract($this->getSelect()), + 'config' => $this->getConfig() + ]; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/QueryFactory.php b/app/code/Magento/Analytics/ReportXml/QueryFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..8ed7e767b28b37f554cbc1e63787ac6508a68773 --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/QueryFactory.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\ReportXml; + +use Magento\Analytics\ReportXml\DB\SelectBuilderFactory; +use Magento\Framework\App\CacheInterface; +use Magento\Framework\ObjectManagerInterface; + +/** + * Class QueryFactory + * + * Creates Query object according to configuration + * Factory for @see \Magento\Analytics\ReportXml\Query + */ +class QueryFactory +{ + /** + * @var Config + */ + private $config; + + /** + * @var SelectBuilderFactory + */ + private $selectBuilderFactory; + + /** + * @var DB\Assembler\AssemblerInterface[] + */ + private $assemblers; + + /** + * @var CacheInterface + */ + private $queryCache; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var SelectHydrator + */ + private $selectHydrator; + + /** + * QueryFactory constructor. + * + * @param CacheInterface $queryCache + * @param SelectHydrator $selectHydrator + * @param ObjectManagerInterface $objectManager + * @param SelectBuilderFactory $selectBuilderFactory + * @param Config $config + * @param array $assemblers + */ + public function __construct( + CacheInterface $queryCache, + SelectHydrator $selectHydrator, + ObjectManagerInterface $objectManager, + SelectBuilderFactory $selectBuilderFactory, + Config $config, + array $assemblers + ) { + $this->config = $config; + $this->selectBuilderFactory = $selectBuilderFactory; + $this->assemblers = $assemblers; + $this->queryCache = $queryCache; + $this->objectManager = $objectManager; + $this->selectHydrator = $selectHydrator; + } + + /** + * Returns query connection name according to configuration + * + * @param string $queryConfig + * @return string + */ + private function getQueryConnectionName($queryConfig) + { + $connectionName = 'default'; + if (isset($queryConfig['connection'])) { + $connectionName = $queryConfig['connection']; + } + return $connectionName; + } + + /** + * Create query according to configuration settings + * + * @param string $queryName + * @return Query + */ + private function constructQuery($queryName) + { + $queryConfig = $this->config->get($queryName); + $selectBuilder = $this->selectBuilderFactory->create(); + $selectBuilder->setConnectionName($this->getQueryConnectionName($queryConfig)); + foreach ($this->assemblers as $assembler) { + $selectBuilder = $assembler->assemble($selectBuilder, $queryConfig); + } + $select = $selectBuilder->create(); + return $this->objectManager->create( + Query::class, + [ + 'select' => $select, + 'selectHydrator' => $this->selectHydrator, + 'connectionName' => $selectBuilder->getConnectionName(), + 'config' => $queryConfig + ] + ); + } + + /** + * Creates query by name + * + * @param string $queryName + * @return Query + */ + public function create($queryName) + { + $cached = $this->queryCache->load($queryName); + if ($cached) { + $queryData = json_decode($cached, true); + return $this->objectManager->create( + Query::class, + [ + 'select' => $this->selectHydrator->recreate($queryData['select_parts']), + 'selectHydrator' => $this->selectHydrator, + 'connectionName' => $queryData['connectionName'], + 'config' => $queryData['config'] + ] + ); + } + $query = $this->constructQuery($queryName); + $this->queryCache->save(json_encode($query), $queryName); + return $query; + } +} diff --git a/app/code/Magento/Analytics/ReportXml/ReportProvider.php b/app/code/Magento/Analytics/ReportXml/ReportProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..3ebe5941108bccb496b9fdae75c8afff8684b94e --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/ReportProvider.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml; + +use Magento\Framework\Api\SearchCriteria; + +/** + * Class ReportProvider + * + * Providers for reports data + */ +class ReportProvider +{ + /** + * @var QueryFactory + */ + private $queryFactory; + + /** + * @var ConnectionFactory + */ + private $connectionFactory; + + /** + * @var IteratorFactory + */ + private $iteratorFactory; + + /** + * ReportProvider constructor. + * + * @param QueryFactory $queryFactory + * @param ConnectionFactory $connectionFactory + * @param IteratorFactory $iteratorFactory + */ + public function __construct( + QueryFactory $queryFactory, + ConnectionFactory $connectionFactory, + IteratorFactory $iteratorFactory + ) { + $this->queryFactory = $queryFactory; + $this->connectionFactory = $connectionFactory; + $this->iteratorFactory = $iteratorFactory; + } + + /** + * Returns custom iterator name for report + * Null for default + * + * @param Query $query + * @return string|null + */ + private function getIteratorName(Query $query) + { + $config = $query->getConfig(); + return isset($config['iterator']) ? $config['iterator'] : null; + } + + /** + * Returns report data by name and criteria + * + * @param string $name + * @return \IteratorIterator + */ + public function getReport($name) + { + $query = $this->queryFactory->create($name); + $connection = $this->connectionFactory->getConnection($query->getConnectionName()); + $statement = $connection->query($query->getSelect()); + return $this->iteratorFactory->create($statement, $this->getIteratorName($query)); + } +} diff --git a/app/code/Magento/Analytics/ReportXml/SelectHydrator.php b/app/code/Magento/Analytics/ReportXml/SelectHydrator.php new file mode 100644 index 0000000000000000000000000000000000000000..02cde2fd0598d156f60ec00d91b1fc9f1052f04e --- /dev/null +++ b/app/code/Magento/Analytics/ReportXml/SelectHydrator.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\ReportXml; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; +use Magento\Framework\ObjectManagerInterface; + +/** + * Class SelectHydrator + */ +class SelectHydrator +{ + /** + * Array of supported Select parts + * + * @var array + */ + private $predefinedSelectParts = + [ + Select::DISTINCT, + Select::COLUMNS, + Select::UNION, + Select::FROM, + Select::WHERE, + Select::GROUP, + Select::HAVING, + Select::ORDER, + Select::LIMIT_COUNT, + Select::LIMIT_OFFSET, + Select::FOR_UPDATE + ]; + + /** + * @var array + */ + private $selectParts; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param ResourceConnection $resourceConnection + * @param ObjectManagerInterface $objectManager + * @param array $selectParts + */ + public function __construct( + ResourceConnection $resourceConnection, + ObjectManagerInterface $objectManager, + $selectParts = [] + ) { + $this->resourceConnection = $resourceConnection; + $this->objectManager = $objectManager; + $this->selectParts = $selectParts; + } + + /** + * @return array + */ + private function getSelectParts() + { + return array_merge($this->predefinedSelectParts, $this->selectParts); + } + + /** + * Extracts Select metadata parts + * + * @param Select $select + * @return array + * @throws \Zend_Db_Select_Exception + */ + public function extract(Select $select) + { + $parts = []; + foreach ($this->getSelectParts() as $partName) { + $parts[$partName] = $select->getPart($partName); + } + return $parts; + } + + /** + * @param array $selectParts + * @return Select + */ + public function recreate(array $selectParts) + { + $select = $this->resourceConnection->getConnection()->select(); + + $select = $this->processColumns($select, $selectParts); + + foreach ($selectParts as $partName => $partValue) { + $select->setPart($partName, $partValue); + } + + return $select; + } + + /** + * Process COLUMNS part values and add this part into select. + * + * If each column contains information about select expression + * an object with the type of this expression going to be created and assigned to this column. + * + * @param Select $select + * @param array $selectParts + * @return Select + */ + private function processColumns(Select $select, array &$selectParts) + { + if (!empty($selectParts[Select::COLUMNS]) && is_array($selectParts[Select::COLUMNS])) { + $part = []; + + foreach ($selectParts[Select::COLUMNS] as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if (is_array($column) && !empty($column['class'])) { + $expression = $this->objectManager->create( + $column['class'], + isset($column['arguments']) ? $column['arguments'] : [] + ); + $part[] = [$correlationName, $expression, $alias]; + } else { + $part[] = $columnEntry; + } + } + + $select->setPart(Select::COLUMNS, $part); + unset($selectParts[Select::COLUMNS]); + } + + return $select; + } +} diff --git a/app/code/Magento/Analytics/Setup/InstallData.php b/app/code/Magento/Analytics/Setup/InstallData.php new file mode 100644 index 0000000000000000000000000000000000000000..aaa619bbb0caa6224213ca5af34ab8e5d94910be --- /dev/null +++ b/app/code/Magento/Analytics/Setup/InstallData.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Setup; + +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\Setup\InstallDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; + +/** + * @codeCoverageIgnore + */ +class InstallData implements InstallDataInterface +{ + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->getConnection()->insertMultiple( + $setup->getTable('core_config_data'), + [ + [ + 'scope' => 'default', + 'scope_id' => 0, + 'path' => 'analytics/subscription/enabled', + 'value' => 1 + ], + [ + 'scope' => 'default', + 'scope_id' => 0, + 'path' => SubscriptionHandler::CRON_STRING_PATH, + 'value' => join(' ', SubscriptionHandler::CRON_EXPR_ARRAY) + ] + ] + ); + + $setup->getConnection()->insert( + $setup->getTable('flag'), + [ + 'flag_code' => SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, + 'state' => 0, + 'flag_data' => 24, + ] + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cbf06264096ac151747a6bafc42d66f2793bbf13 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Block\Adminhtml\System\Config; + +use Magento\Analytics\Block\Adminhtml\System\Config\AdditionalComment; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Data\Form; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class AdditionalCommentTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var AdditionalComment + */ + private $additionalComment; + + /** + * @var AbstractElement|\PHPUnit_Framework_MockObject_MockObject + */ + private $abstractElementMock; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var Form|\PHPUnit_Framework_MockObject_MockObject + */ + private $formMock; + + protected function setUp() + { + $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment', 'getLabel']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManager($this); + $this->additionalComment = $objectManager->getObject( + AdditionalComment::class, + [ + 'context' => $this->contextMock + ] + ); + } + + public function testRender() + { + $this->abstractElementMock->setForm($this->formMock); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('New comment'); + $this->abstractElementMock->expects($this->any()) + ->method('getLabel') + ->willReturn('Comment label'); + $html = $this->additionalComment->render($this->abstractElementMock); + $this->assertRegexp( + "/New comment/", + $html + ); + $this->assertRegexp( + "/Comment label/", + $html + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a652cf6b3d548cdfb6c1292e9d57716d0afa3918 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Block\Adminhtml\System\Config; + +use Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Data\Form; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class CollectionTimeLabelTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var CollectionTimeLabel + */ + private $collectionTimeLabel; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $timeZoneMock; + + /** + * @var AbstractElement|\PHPUnit_Framework_MockObject_MockObject + */ + private $abstractElementMock; + + protected function setUp() + { + $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->setMethods(['getLocaleDate']) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + $this->timeZoneMock = $this->getMockBuilder(TimezoneInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock->expects($this->any()) + ->method('getLocaleDate') + ->willReturn($this->timeZoneMock); + + $objectManager = new ObjectManager($this); + $this->collectionTimeLabel = $objectManager->getObject( + CollectionTimeLabel::class, + [ + 'context' => $this->contextMock + ] + ); + } + + public function testRender() + { + $timeZone = "America/New_York"; + $this->abstractElementMock->setForm($this->formMock); + $this->timeZoneMock->expects($this->once()) + ->method('getConfigTimezone') + ->willReturn($timeZone); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('Eastern Standard Time (America/New_York)'); + $this->assertRegexp( + "/Eastern Standard Time \(America\/New_York\)/", + $this->collectionTimeLabel->render($this->abstractElementMock) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php new file mode 100644 index 0000000000000000000000000000000000000000..09e753e4ac8aa5df30be98a721825dbae030f446 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Block\Adminhtml\System\Config; + +use Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel; +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Data\Form; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * Class SignupTest + */ +class SubscriptionStatusLabelTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SubscriptionStatusLabel + */ + private $subscriptionStatusLabel; + + /** + * @var AbstractElement|\PHPUnit_Framework_MockObject_MockObject + */ + private $abstractElementMock; + + /** + * @var SubscriptionStatusProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $subscriptionStatusProviderMock; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var Form|\PHPUnit_Framework_MockObject_MockObject + */ + private $formMock; + + protected function setUp() + { + $this->subscriptionStatusProviderMock = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment']) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManager($this); + $this->subscriptionStatusLabel = $objectManager->getObject( + SubscriptionStatusLabel::class, + [ + 'context' => $this->contextMock, + 'subscriptionStatusProvider' => $this->subscriptionStatusProviderMock + ] + ); + } + + public function testRender() + { + $this->abstractElementMock->setForm($this->formMock); + $this->subscriptionStatusProviderMock->expects($this->once()) + ->method('getStatus') + ->willReturn('Enabled'); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('Subscription status: Enabled'); + $this->assertRegexp( + "/Subscription status: Enabled/", + $this->subscriptionStatusLabel->render($this->abstractElementMock) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..abce48c36c86af40ced174feba180feea85a33a2 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Block\Adminhtml\System\Config; + +use Magento\Analytics\Block\Adminhtml\System\Config\Vertical; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Data\Form; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class VerticalTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Vertical + */ + private $vertical; + + /** + * @var AbstractElement|\PHPUnit_Framework_MockObject_MockObject + */ + private $abstractElementMock; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var Form|\PHPUnit_Framework_MockObject_MockObject + */ + private $formMock; + + protected function setUp() + { + $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) + ->setMethods(['getComment', 'getLabel', 'getHint']) + ->disableOriginalConstructor() + ->getMock(); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->formMock = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManager($this); + $this->vertical = $objectManager->getObject( + Vertical::class, + [ + 'context' => $this->contextMock + ] + ); + } + + public function testRender() + { + $this->abstractElementMock->setForm($this->formMock); + $this->abstractElementMock->expects($this->any()) + ->method('getComment') + ->willReturn('New comment'); + $this->abstractElementMock->expects($this->any()) + ->method('getHint') + ->willReturn('New hint'); + $html = $this->vertical->render($this->abstractElementMock); + $this->assertRegexp( + "/New comment/", + $html + ); + $this->assertRegExp( + "/New hint/", + $html + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BIEssentials/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BIEssentials/SignUpTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6f613cdc4d639e51f47600b7bfbbeced9858c588 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/BIEssentials/SignUpTest.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Controller\Adminhtml\BIEssentials; + +use Magento\Analytics\Controller\Adminhtml\BIEssentials\SignUp; +use Magento\Backend\Model\View\Result\RedirectFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class SignupTest + */ +class SignUpTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var SignUp + */ + private $signUpController; + + /** + * @var RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectFactoryMock; + + /** + * @var Redirect|\PHPUnit_Framework_MockObject_MockObject + */ + private $redirectMock; + + /** + * @return void + */ + protected function setUp() + { + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultRedirectFactoryMock = $this->getMockBuilder(RedirectFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->signUpController = $this->objectManagerHelper->getObject( + SignUp::class, + [ + 'config' => $this->configMock, + 'resultRedirectFactory' => $this->resultRedirectFactoryMock + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $urlBIEssentialsConfigPath = 'analytics/url/bi_essentials'; + $this->configMock->expects($this->once()) + ->method('getValue') + ->with($urlBIEssentialsConfigPath) + ->willReturn('value'); + $this->resultRedirectFactoryMock->expects($this->once())->method('create')->willReturn($this->redirectMock); + $this->redirectMock->expects($this->once())->method('setUrl')->with('value')->willReturnSelf(); + $this->assertEquals($this->redirectMock, $this->signUpController->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4f54ce5059965c2fb10181c4f623c1fd1b9d9ee2 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Reports/ShowTest.php @@ -0,0 +1,185 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Controller\Adminhtml\Reports; + +use Magento\Analytics\Controller\Adminhtml\Reports\Show; +use Magento\Analytics\Model\Exception\State\SubscriptionUpdateException; +use Magento\Analytics\Model\ReportUrlProvider; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ShowTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ReportUrlProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $reportUrlProviderMock; + + /** + * @var Redirect|\PHPUnit_Framework_MockObject_MockObject + */ + private $redirectMock; + + /** + * @var ResultFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultFactoryMock; + + /** + * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $messageManagerMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Show + */ + private $showController; + + /** + * @return void + */ + protected function setUp() + { + $this->reportUrlProviderMock = $this->getMockBuilder(ReportUrlProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->redirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->showController = $this->objectManagerHelper->getObject( + Show::class, + [ + 'reportUrlProvider' => $this->reportUrlProviderMock, + 'resultFactory' => $this->resultFactoryMock, + 'messageManager' => $this->messageManagerMock, + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $otpUrl = 'http://example.com?otp=15vbjcfdvd15645'; + + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + $this->reportUrlProviderMock + ->expects($this->once()) + ->method('getUrl') + ->with() + ->willReturn($otpUrl); + $this->redirectMock + ->expects($this->once()) + ->method('setUrl') + ->with($otpUrl) + ->willReturnSelf(); + $this->assertSame($this->redirectMock, $this->showController->execute()); + } + + /** + * @dataProvider executeWithExceptionDataProvider + * + * @param \Exception $exception + */ + public function testExecuteWithException(\Exception $exception) + { + + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + $this->reportUrlProviderMock + ->expects($this->once()) + ->method('getUrl') + ->with() + ->willThrowException($exception); + if ($exception instanceof LocalizedException) { + $message = $exception->getMessage(); + } else { + $message = __('Sorry, there has been an error processing your request. Please try again later.'); + } + $this->messageManagerMock + ->expects($this->once()) + ->method('addExceptionMessage') + ->with($exception, $message) + ->willReturnSelf(); + $this->redirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->assertSame($this->redirectMock, $this->showController->execute()); + } + + /** + * @return array + */ + public function executeWithExceptionDataProvider() + { + return [ + 'ExecuteWithLocalizedException' => [new LocalizedException(__('TestMessage'))], + 'ExecuteWithException' => [new \Exception('TestMessage')], + ]; + } + + /** + * @return void + */ + public function testExecuteWithSubscriptionUpdateException() + { + $exception = new SubscriptionUpdateException(__('TestMessage')); + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->redirectMock); + $this->reportUrlProviderMock + ->expects($this->once()) + ->method('getUrl') + ->with() + ->willThrowException($exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addNoticeMessage') + ->with($exception->getMessage()) + ->willReturnSelf(); + $this->redirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->assertSame($this->redirectMock, $this->showController->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/RetryTest.php b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/RetryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..17c485a8df230c14ed771f88f2a84167427b29ff --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Controller/Adminhtml/Subscription/RetryTest.php @@ -0,0 +1,159 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Controller\Adminhtml\Subscription; + +use Magento\Analytics\Controller\Adminhtml\Subscription\Retry; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Message\ManagerInterface; +use Magento\Framework\Phrase; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class RetryTest + */ +class RetryTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ResultFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultFactoryMock; + + /** + * @var Redirect|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectMock; + + /** + * @var SubscriptionHandler|\PHPUnit_Framework_MockObject_MockObject + */ + private $subscriptionHandlerMock; + + /** + * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $messageManagerMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Retry + */ + private $retryController; + + /** + * @return void + */ + protected function setUp() + { + $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resultRedirectMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subscriptionHandlerMock = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->retryController = $this->objectManagerHelper->getObject( + Retry::class, + [ + 'resultFactory' => $this->resultFactoryMock, + 'subscriptionHandler' => $this->subscriptionHandlerMock, + 'messageManager' => $this->messageManagerMock, + ] + ); + } + + /** + * @return void + */ + public function testExecute() + { + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->resultRedirectMock); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willReturn(true); + $this->assertSame( + $this->resultRedirectMock, + $this->retryController->execute() + ); + } + + /** + * @dataProvider executeExceptionsDataProvider + * + * @param \Exception $exception + * @param Phrase $message + */ + public function testExecuteWithException(\Exception $exception, Phrase $message) + { + $this->resultFactoryMock + ->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_REDIRECT) + ->willReturn($this->resultRedirectMock); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('adminhtml') + ->willReturnSelf(); + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willThrowException($exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addExceptionMessage') + ->with($exception, $message); + + $this->assertSame( + $this->resultRedirectMock, + $this->retryController->execute() + ); + } + + /** + * @return array + */ + public function executeExceptionsDataProvider() + { + return [ + [new LocalizedException(__('TestMessage')), __('TestMessage')], + [ + new \Exception('TestMessage'), + __('Sorry, there has been an error processing your request. Please try again later.') + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php new file mode 100644 index 0000000000000000000000000000000000000000..81c57d79033c86f87e3530c0852973f93d7ec356 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/CollectDataTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Cron; + +use Magento\Analytics\Cron\CollectData; +use Magento\Analytics\Model\ExportDataHandlerInterface; +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class CollectDataTest + */ +class CollectDataTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ExportDataHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $exportDataHandlerMock; + + /** + * @var SubscriptionStatusProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $subscriptionStatusMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var CollectData + */ + private $collectData; + + /** + * @return void + */ + protected function setUp() + { + $this->exportDataHandlerMock = $this->getMockBuilder(ExportDataHandlerInterface::class) + ->getMockForAbstractClass(); + + $this->subscriptionStatusMock = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->collectData = $this->objectManagerHelper->getObject( + CollectData::class, + [ + 'exportDataHandler' => $this->exportDataHandlerMock, + 'subscriptionStatus' => $this->subscriptionStatusMock, + ] + ); + } + + /** + * @param string $status + * @return void + * @dataProvider executeDataProvider + */ + public function testExecute($status) + { + $this->subscriptionStatusMock + ->expects($this->once()) + ->method('getStatus') + ->with() + ->willReturn($status); + $this->exportDataHandlerMock + ->expects(($status === SubscriptionStatusProvider::ENABLED) ? $this->once() : $this->never()) + ->method('prepareExportData') + ->with(); + + $this->assertTrue($this->collectData->execute()); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + 'Subscription is enabled' => [SubscriptionStatusProvider::ENABLED], + 'Subscription is disabled' => [SubscriptionStatusProvider::DISABLED], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php new file mode 100644 index 0000000000000000000000000000000000000000..959a11f9e10586d7f919198b15805e80b5f3c936 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/SignUpTest.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Cron; + +use Magento\Analytics\Cron\SignUp; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Analytics\Model\Connector; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SignUpTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Connector|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectorMock; + + /** + * @var WriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriterMock; + + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $reinitableConfigMock; + + /** + * @var SignUp + */ + private $signUp; + + protected function setUp() + { + $this->connectorMock = $this->getMockBuilder(Connector::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->signUp = new SignUp( + $this->connectorMock, + $this->configWriterMock, + $this->flagManagerMock, + $this->reinitableConfigMock + ); + } + + public function testExecute() + { + $attemptsCount = 10; + + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($attemptsCount); + + $attemptsCount -= 1; + $this->flagManagerMock->expects($this->once()) + ->method('saveFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $attemptsCount); + $this->connectorMock->expects($this->once()) + ->method('execute') + ->with('signUp') + ->willReturn(true); + $this->addDeleteAnalyticsCronExprAsserts(); + $this->flagManagerMock->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + $this->assertTrue($this->signUp->execute()); + } + + public function testExecuteFlagNotExist() + { + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(null); + $this->addDeleteAnalyticsCronExprAsserts(); + $this->assertFalse($this->signUp->execute()); + } + + public function testExecuteZeroAttempts() + { + $attemptsCount = 0; + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($attemptsCount); + $this->addDeleteAnalyticsCronExprAsserts(); + $this->flagManagerMock->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + $this->assertFalse($this->signUp->execute()); + } + + /** + * Add assertions for method deleteAnalyticsCronExpr. + * + * @return void + */ + private function addDeleteAnalyticsCronExprAsserts() + { + $this->configWriterMock + ->expects($this->once()) + ->method('delete') + ->with(SubscriptionHandler::CRON_STRING_PATH) + ->willReturn(true); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->willReturnSelf(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ede53d8783a7a7b3f9f33c536a940f002f1eda35 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php @@ -0,0 +1,214 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Cron; + +use Magento\Analytics\Cron\Update; +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; + +/** + * Class Update + */ +class UpdateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Connector|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectorMock; + + /** + * @var WriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriterMock; + + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $reinitableConfigMock; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var Update + */ + private $update; + + protected function setUp() + { + $this->connectorMock = $this->getMockBuilder(Connector::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->update = new Update( + $this->connectorMock, + $this->configWriterMock, + $this->reinitableConfigMock, + $this->flagManagerMock, + $this->analyticsTokenMock + ); + } + + /** + * @return void + */ + public function testExecuteWithoutToken() + { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(10); + $this->connectorMock + ->expects($this->once()) + ->method('execute') + ->with('update') + ->willReturn(false); + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->addFinalOutputAsserts(); + $this->assertFalse($this->update->execute()); + } + + /** + * @param bool $isExecuted + */ + private function addFinalOutputAsserts(bool $isExecuted = true) + { + $this->flagManagerMock + ->expects($this->exactly(2 * $isExecuted)) + ->method('deleteFlag') + ->withConsecutive( + [SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE], + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE] + ); + $this->configWriterMock + ->expects($this->exactly((int)$isExecuted)) + ->method('delete') + ->with(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH); + $this->reinitableConfigMock + ->expects($this->exactly((int)$isExecuted)) + ->method('reinit') + ->with(); + } + + /** + * @param $counterData + * @return void + * @dataProvider executeWithEmptyReverseCounterDataProvider + */ + public function testExecuteWithEmptyReverseCounter($counterData) + { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($counterData); + $this->connectorMock + ->expects($this->never()) + ->method('execute') + ->with('update') + ->willReturn(false); + $this->analyticsTokenMock + ->method('isTokenExist') + ->willReturn(true); + $this->addFinalOutputAsserts(); + $this->assertFalse($this->update->execute()); + } + + /** + * Provides empty states of the reverse counter. + * + * @return array + */ + public function executeWithEmptyReverseCounterDataProvider() + { + return [ + [null], + [0] + ]; + } + + /** + * @param int $reverseCount + * @param bool $commandResult + * @param bool $finalConditionsIsExpected + * @param bool $functionResult + * @return void + * @dataProvider executeRegularScenarioDataProvider + */ + public function testExecuteRegularScenario( + int $reverseCount, + bool $commandResult, + bool $finalConditionsIsExpected, + bool $functionResult + ) { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) + ->willReturn($reverseCount); + $this->connectorMock + ->expects($this->once()) + ->method('execute') + ->with('update') + ->willReturn($commandResult); + $this->analyticsTokenMock + ->method('isTokenExist') + ->willReturn(true); + $this->addFinalOutputAsserts($finalConditionsIsExpected); + $this->assertSame($functionResult, $this->update->execute()); + } + + /** + * @return array + */ + public function executeRegularScenarioDataProvider() + { + return [ + 'The last attempt with command execution result False' => [ + 'Reverse count' => 1, + 'Command result' => false, + 'Executed final output conditions' => true, + 'Function result' => false, + ], + 'Not the last attempt with command execution result False' => [ + 'Reverse count' => 10, + 'Command result' => false, + 'Executed final output conditions' => false, + 'Function result' => false, + ], + 'Command execution result True' => [ + 'Reverse count' => 10, + 'Command result' => true, + 'Executed final output conditions' => true, + 'Function result' => true, + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php b/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php new file mode 100644 index 0000000000000000000000000000000000000000..57315543bc32dec0258d4b1e76e79fd6b5c965f3 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/AnalyticsTokenTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class AnalyticsTokenTest + */ +class AnalyticsTokenTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $reinitableConfigMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var WriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriterMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var AnalyticsToken + */ + private $tokenModel; + + /** + * @var string + */ + private $tokenPath = 'analytics/general/token'; + + /** + * @return void + */ + protected function setUp() + { + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->tokenModel = $this->objectManagerHelper->getObject( + AnalyticsToken::class, + [ + 'reinitableConfig' => $this->reinitableConfigMock, + 'config' => $this->configMock, + 'configWriter' => $this->configWriterMock, + 'tokenPath' => $this->tokenPath, + ] + ); + } + + /** + * @return void + */ + public function testStoreToken() + { + $value = 'jjjj0000'; + + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with($this->tokenPath, $value); + + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->willReturnSelf(); + + $this->assertTrue($this->tokenModel->storeToken($value)); + } + + /** + * @return void + */ + public function testGetToken() + { + $value = 'jjjj0000'; + + $this->configMock + ->expects($this->once()) + ->method('getValue') + ->with($this->tokenPath) + ->willReturn($value); + + $this->assertSame($value, $this->tokenModel->getToken()); + } + + /** + * @return void + */ + public function testIsTokenExist() + { + $this->assertFalse($this->tokenModel->isTokenExist()); + + $this->configMock + ->expects($this->once()) + ->method('getValue') + ->with($this->tokenPath) + ->willReturn('0000'); + $this->assertTrue($this->tokenModel->isTokenExist()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Baseurl/SubscriptionUpdateHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Baseurl/SubscriptionUpdateHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f5f721c038c57fd87f968863cf63d18f8ba2eba8 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Baseurl/SubscriptionUpdateHandlerTest.php @@ -0,0 +1,178 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Model\Config\Backend\Baseurl; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class SubscriptionUpdateHandlerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $reinitableConfigMock; + + /** + * @var WriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriterMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var SubscriptionUpdateHandler + */ + private $subscriptionUpdateHandler; + + /** + * @var int + */ + private $attemptsInitValue = 48; + + /** + * @var string + */ + private $cronExpression = '0 * * * *'; + + /** + * @return void + */ + protected function setUp() + { + $this->reinitableConfigMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->subscriptionUpdateHandler = $this->objectManagerHelper->getObject( + SubscriptionUpdateHandler::class, + [ + 'reinitableConfig' => $this->reinitableConfigMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'flagManager' => $this->flagManagerMock, + 'configWriter' => $this->configWriterMock, + ] + ); + } + + /** + * @return void + */ + public function testTokenDoesNotExist() + { + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn(false); + $this->flagManagerMock + ->expects($this->never()) + ->method('saveFlag'); + $this->configWriterMock + ->expects($this->never()) + ->method('save'); + $this->assertTrue($this->subscriptionUpdateHandler->processUrlUpdate('http://store.com')); + } + + /** + * @return void + */ + public function testTokenAndPreviousBaseUrlExist() + { + $url = 'https://store.com'; + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn(true); + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn(true); + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->withConsecutive( + [SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue], + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, $url] + ); + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH, $this->cronExpression); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->with(); + $this->assertTrue($this->subscriptionUpdateHandler->processUrlUpdate($url)); + } + + /** + * @return void + */ + public function testTokenExistAndWithoutPreviousBaseUrl() + { + $url = 'https://store.com'; + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn(true); + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn(false); + $this->flagManagerMock + ->expects($this->exactly(2)) + ->method('saveFlag') + ->withConsecutive( + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, $url], + [SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue] + ); + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH, $this->cronExpression); + $this->reinitableConfigMock + ->expects($this->once()) + ->method('reinit') + ->with(); + $this->assertTrue($this->subscriptionUpdateHandler->processUrlUpdate($url)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..071b96111ac8bcfc5f8caebbd736fa29f03b88ad --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/CollectionTimeTest.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Config\Backend; + +use Magento\Analytics\Model\Config\Backend\CollectionTime; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\App\Config\Value; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Psr\Log\LoggerInterface; + +/** + * Class CollectionTimeTest + */ +class CollectionTimeTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var WriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriterMock; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var CollectionTime + */ + private $collectionTime; + + /** + * @return void + */ + protected function setUp() + { + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->collectionTime = $this->objectManagerHelper->getObject( + CollectionTime::class, + [ + 'configWriter' => $this->configWriterMock, + '_logger' => $this->loggerMock, + ] + ); + } + + /** + * @return void + */ + public function testAfterSave() + { + $this->collectionTime->setData('value', '05,04,03'); + + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(CollectionTime::CRON_SCHEDULE_PATH, join(' ', ['04', '05', '*', '*', '*'])); + + $this->assertInstanceOf( + Value::class, + $this->collectionTime->afterSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testAfterSaveWrongValue() + { + $this->collectionTime->setData('value', '00,01'); + $this->collectionTime->afterSave(); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testAfterSaveWithLocalizedException() + { + $exception = new \Exception('Test message'); + $this->collectionTime->setData('value', '05,04,03'); + + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(CollectionTime::CRON_SCHEDULE_PATH, join(' ', ['04', '05', '*', '*', '*'])) + ->willThrowException($exception); + $this->loggerMock + ->expects($this->once()) + ->method('error') + ->with($exception->getMessage()); + $this->collectionTime->afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..82aa4dc72dfe08ae22f3e5dc7fcf050291f00720 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/Enabled/SubscriptionHandlerTest.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Model\Config\Backend\Enabled; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\CollectionTime; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class SubscriptionHandlerTest + */ +class SubscriptionHandlerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var WriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriterMock; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $tokenMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var int + */ + private $attemptsInitValue = 10; + + /** + * @var SubscriptionHandler + */ + private $subscriptionHandler; + + protected function setUp() + { + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configWriterMock = $this->getMockBuilder(WriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->tokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->subscriptionHandler = $this->objectManagerHelper->getObject( + SubscriptionHandler::class, + [ + 'flagManager' => $this->flagManagerMock, + 'configWriter' => $this->configWriterMock, + 'attemptsInitValue' => $this->attemptsInitValue, + 'analyticsToken' => $this->tokenMock, + ] + ); + } + + public function testProcessEnabledTokenExist() + { + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->configWriterMock + ->expects($this->never()) + ->method('save'); + $this->flagManagerMock + ->expects($this->never()) + ->method('saveFlag'); + $this->assertTrue( + $this->subscriptionHandler->processEnabled() + ); + } + + public function testProcessEnabledTokenDoesNotExist() + { + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->configWriterMock + ->expects($this->once()) + ->method('save') + ->with(SubscriptionHandler::CRON_STRING_PATH, "0 * * * *"); + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $this->attemptsInitValue) + ->willReturn(true); + $this->assertTrue( + $this->subscriptionHandler->processEnabled() + ); + } + + public function testProcessDisabledTokenDoesNotExist() + { + $this->configWriterMock + ->expects($this->once()) + ->method('delete') + ->with(CollectionTime::CRON_SCHEDULE_PATH); + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->flagManagerMock + ->expects($this->once()) + ->method('deleteFlag') + ->with(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE) + ->willReturn(true); + $this->assertTrue( + $this->subscriptionHandler->processDisabled() + ); + } + + public function testProcessDisabledTokenExists() + { + $this->configWriterMock + ->expects($this->once()) + ->method('delete') + ->with(CollectionTime::CRON_SCHEDULE_PATH); + $this->tokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->flagManagerMock + ->expects($this->never()) + ->method('deleteFlag'); + $this->assertTrue( + $this->subscriptionHandler->processDisabled() + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php new file mode 100644 index 0000000000000000000000000000000000000000..eea3193258bc6c3f7a48a9d4578058a4d1d96b2d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/EnabledTest.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Model\Config\Backend; + +use Magento\Analytics\Model\Config\Backend\Enabled; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Value; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Psr\Log\LoggerInterface; + +/** + * Class EnabledTest + */ +class EnabledTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SubscriptionHandler|\PHPUnit_Framework_MockObject_MockObject + */ + private $subscriptionHandlerMock; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var Value|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Enabled + */ + private $enabledModel; + + /** + * @var int + */ + private $valueEnabled = 1; + + /** + * @var int + */ + private $valueDisabled = 0; + + /** + * @return void + */ + protected function setUp() + { + $this->subscriptionHandlerMock = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->enabledModel = $this->objectManagerHelper->getObject( + Enabled::class, + [ + 'subscriptionHandler' => $this->subscriptionHandlerMock, + '_logger' => $this->loggerMock, + 'config' => $this->configMock, + ] + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccessEnabled() + { + $this->enabledModel->setData('value', $this->valueEnabled); + + $this->configMock + ->expects($this->any()) + ->method('getValue') + ->willReturn(!$this->valueEnabled); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccessDisabled() + { + $this->enabledModel->setData('value', $this->valueDisabled); + + $this->configMock + ->expects($this->any()) + ->method('getValue') + ->willReturn(!$this->valueDisabled); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processDisabled') + ->with() + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + */ + public function testAfterSaveSuccessValueNotChanged() + { + $this->enabledModel->setData('value', null); + + $this->configMock + ->expects($this->any()) + ->method('getValue') + ->willReturn(null); + + $this->subscriptionHandlerMock + ->expects($this->never()) + ->method('processEnabled') + ->with() + ->willReturn(true); + $this->subscriptionHandlerMock + ->expects($this->never()) + ->method('processDisabled') + ->with() + ->willReturn(true); + + $this->assertInstanceOf( + Value::class, + $this->enabledModel->afterSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testExecuteAfterSaveFailedWithLocalizedException() + { + $exception = new \Exception('Message'); + $this->enabledModel->setData('value', $this->valueEnabled); + + $this->subscriptionHandlerMock + ->expects($this->once()) + ->method('processEnabled') + ->with() + ->willThrowException($exception); + + $this->loggerMock + ->expects($this->once()) + ->method('error') + ->with($exception->getMessage()); + + $this->enabledModel->afterSave(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6fe7d0aa93998b2eafbba5745ddbaae59fc89c72 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Backend/VerticalTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Config\Backend; + +/** + * A unit test for testing of the backend model for verticals configuration. + */ +class VerticalTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\Model\Config\Backend\Vertical + */ + private $subject; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Config\Backend\Vertical::class + ); + } + + /** + * @return void + */ + public function testBeforeSaveSuccess() + { + $this->subject->setValue('Apps and Games'); + + $this->assertInstanceOf( + \Magento\Analytics\Model\Config\Backend\Vertical::class, + $this->subject->beforeSave() + ); + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testBeforeSaveFailedWithLocalizedException() + { + $this->subject->setValue(''); + + $this->subject->beforeSave(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0b7f4870dbac8c956ea8107f5815883141997704 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/MapperTest.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Config; + +use Magento\Analytics\Model\Config\Mapper; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MapperTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Mapper + */ + private $mapper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->mapper = $this->objectManagerHelper->getObject(Mapper::class); + } + + /** + * @param array $configData + * @param array $resultData + * @return void + * + * @dataProvider executingDataProvider + */ + public function testExecution($configData, $resultData) + { + $this->assertSame($resultData, $this->mapper->execute($configData)); + } + + /** + * @return array + */ + public function executingDataProvider() + { + return [ + 'wrongConfig' => [ + ['config' => ['files']], + [] + ], + 'validConfigWithFileNodes' => [ + [ + 'config' => [ + 0 => [ + 'file' => [ + 0 => [ + 'name' => 'fileName', + 'providers' => [[]] + ] + ] + ] + ] + ], + [ + 'fileName' => [ + 'name' => 'fileName', + 'providers' => [] + ] + ], + ], + 'validConfigWithProvidersNode' => [ + [ + 'config' => [ + 0 => [ + 'file' => [ + 0 => [ + 'name' => 'fileName', + 'providers' => [ + 0 => [ + 'reportProvider' => [0 => []] + ] + ] + ] + ] + ] + ] + ], + [ + 'fileName' => [ + 'name' => 'fileName', + 'providers' => [ + 'reportProvider' => ['parameters' => []] + ] + ] + ], + ], + 'validConfigWithParametersNode' => [ + [ + 'config' => [ + 0 => [ + 'file' => [ + 0 => [ + 'name' => 'fileName', + 'providers' => [ + 0 => [ + 'reportProvider' => [ + 0 => [ + 'parameters' => [ + 0 => ['name' => ['reportName']] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ], + [ + 'fileName' => [ + 'name' => 'fileName', + 'providers' => [ + 'reportProvider' => [ + 'parameters' => [ + 'name' => 'reportName' + ] + ] + ] + ] + ], + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6aa9c7ef3106c93eebaa4974a773433260330c5d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/ReaderTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Config; + +use Magento\Analytics\Model\Config\Mapper; +use Magento\Analytics\Model\Config\Reader; +use Magento\Framework\Config\ReaderInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ReaderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Mapper|\PHPUnit_Framework_MockObject_MockObject + */ + private $mapperMock; + + /** + * @var ReaderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $readerXmlMock; + + /** + * @var ReaderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $readerDbMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Reader + */ + private $reader; + + /** + * @return void + */ + protected function setUp() + { + $this->mapperMock = $this->getMockBuilder(Mapper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->readerXmlMock = $this->getMockBuilder(ReaderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->readerDbMock = $this->getMockBuilder(ReaderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reader = $this->objectManagerHelper->getObject( + Reader::class, + [ + 'mapper' => $this->mapperMock, + 'readers' => [ + $this->readerXmlMock, + $this->readerDbMock, + ], + ] + ); + } + + /** + * @return void + */ + public function testRead() + { + $scope = 'store'; + $xmlReaderResult = [ + 'config' => ['node1' => ['node2' => 'node4']] + ]; + $dbReaderResult = [ + 'config' => ['node1' => ['node2' => 'node3']] + ]; + $mapperResult = ['node2' => ['node3', 'node4']]; + + $this->readerXmlMock + ->expects($this->once()) + ->method('read') + ->with($scope) + ->willReturn($xmlReaderResult); + + $this->readerDbMock + ->expects($this->once()) + ->method('read') + ->with($scope) + ->willReturn($dbReaderResult); + + $this->mapperMock + ->expects($this->once()) + ->method('execute') + ->with(array_merge_recursive($xmlReaderResult, $dbReaderResult)) + ->willReturn($mapperResult); + + $this->assertSame($mapperResult, $this->reader->read($scope)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c13205d34f25b5b43812f0872ba386ac9a13e658 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Config/Source/VerticalTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Config\Source; + +/** + * A unit test for testing of the source model for verticals configuration. + */ +class VerticalTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\Model\Config\Source\Vertical + */ + private $subject; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Config\Source\Vertical::class, + [ + 'verticals' => [ + 'Apps and Games', + 'Athletic/Sporting Goods', + 'Art and Design' + ] + ] + ); + } + + /** + * @return void + */ + public function testToOptionArray() + { + $expectedOptionsArray = [ + ['value' => '', 'label' => __('--Please Select--')], + ['value' => 'Apps and Games', 'label' => __('Apps and Games')], + ['value' => 'Athletic/Sporting Goods', 'label' => __('Athletic/Sporting Goods')], + ['value' => 'Art and Design', 'label' => __('Art and Design')] + ]; + + $this->assertEquals( + $expectedOptionsArray, + $this->subject->toOptionArray() + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8739219ebdf0947051490f82b2a53c942254c6a9 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ConfigTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\Config; +use Magento\Framework\Config\DataInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ConfigTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var DataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataInterfaceMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Config + */ + private $config; + + /** + * @return void + */ + protected function setUp() + { + $this->dataInterfaceMock = $this->getMockBuilder(DataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->config = $this->objectManagerHelper->getObject( + Config::class, + [ + 'data' => $this->dataInterfaceMock, + ] + ); + } + + /** + * @return void + */ + public function testGet() + { + $key = 'configKey'; + $defaultValue = 'mock'; + $configValue = 'emptyString'; + + $this->dataInterfaceMock + ->expects($this->once()) + ->method('get') + ->with($key, $defaultValue) + ->willReturn($configValue); + + $this->assertSame($configValue, $this->config->get($key, $defaultValue)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f8f3919b2489eab3b3e1fe10c6eb4316307ab4d9 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php @@ -0,0 +1,218 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\Http\Client; + +use Magento\Analytics\Model\Connector\Http\ConverterInterface; +use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Framework\HTTP\Adapter\CurlFactory; + +/** + * A unit test for testing of the CURL HTTP client. + */ +class CurlTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\Model\Connector\Http\Client\Curl + */ + private $subject; + + /** + * @var \Magento\Framework\HTTP\Adapter\Curl|\PHPUnit_Framework_MockObject_MockObject + */ + private $curlMock; + + /** + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var CurlFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $curlFactoryMock; + + /** + * @var \Magento\Analytics\Model\Connector\Http\ResponseFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseFactoryMock; + + /** + * @var ConverterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $converterMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->curlMock = $this->getMockBuilder( + \Magento\Framework\HTTP\Adapter\Curl::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder( + \Psr\Log\LoggerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->curlFactoryMock = $this->getMockBuilder(CurlFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->curlFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($this->curlMock); + + $this->responseFactoryMock = $this->getMockBuilder( + \Magento\Analytics\Model\Connector\Http\ResponseFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->converterMock = $this->createJsonConverter(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\Model\Connector\Http\Client\Curl::class, + [ + 'curlFactory' => $this->curlFactoryMock, + 'responseFactory' => $this->responseFactoryMock, + 'converter' => $this->converterMock, + 'logger' => $this->loggerMock, + ] + ); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + public function getTestData() + { + return [ + [ + 'data' => [ + 'version' => '1.1', + 'body'=> ['name' => 'value'], + 'url' => 'http://www.mystore.com', + 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + ] + ] + ]; + } + + /** + * @return void + * @dataProvider getTestData + */ + public function testRequestSuccess(array $data) + { + $responseString = 'This is response.'; + $response = new \Zend_Http_Response(201, [], $responseString); + $this->curlMock->expects($this->once()) + ->method('write') + ->with( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + json_encode($data['body']) + ); + $this->curlMock->expects($this->once()) + ->method('read') + ->willReturn($responseString); + $this->curlMock->expects($this->any()) + ->method('getErrno') + ->willReturn(0); + + $this->responseFactoryMock->expects($this->any()) + ->method('create') + ->with($responseString) + ->willReturn($response); + + $this->assertEquals( + $response, + $this->subject->request( + $data['method'], + $data['url'], + $data['body'], + $data['headers'], + $data['version'] + ) + ); + } + + /** + * @return void + * @dataProvider getTestData + */ + public function testRequestError(array $data) + { + $response = new \Zend_Http_Response(0, []); + $this->curlMock->expects($this->once()) + ->method('write') + ->with( + $data['method'], + $data['url'], + $data['version'], + $data['headers'], + json_encode($data['body']) + ); + $this->curlMock->expects($this->once()) + ->method('read'); + $this->curlMock->expects($this->atLeastOnce()) + ->method('getErrno') + ->willReturn(1); + $this->curlMock->expects($this->atLeastOnce()) + ->method('getError') + ->willReturn('CURL error.'); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with( + new \Exception( + 'MBI service CURL connection error #1: CURL error.' + ) + ); + + $this->assertEquals( + $response, + $this->subject->request( + $data['method'], + $data['url'], + $data['body'], + $data['headers'], + $data['version'] + ) + ); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createJsonConverter() + { + $converterMock = $this->getMockBuilder(ConverterInterface::class) + ->getMockForAbstractClass(); + $converterMock->expects($this->any())->method('toBody')->willReturnCallback(function ($value) { + return json_encode($value); + }); + $converterMock->expects($this->any()) + ->method('getContentTypeHeader') + ->willReturn(JsonConverter::CONTENT_TYPE_HEADER); + return $converterMock; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..60a19f3d5079ea953df703f92c830851892b75ac --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\Http; + +use Magento\Analytics\Model\Connector\Http\JsonConverter; + +/** + * Class JsonConverterTest + */ +class JsonConverterTest extends \PHPUnit\Framework\TestCase +{ + public function testConverterContainsHeader() + { + $converter = new JsonConverter(); + $this->assertEquals(JsonConverter::CONTENT_TYPE_HEADER, $converter->getContentTypeHeader()); + } + + public function testConvertBody() + { + $body = '{"token": "secret-token"}'; + $converter = new JsonConverter(); + $this->assertEquals(json_decode($body, 1), $converter->fromBody($body)); + } + + public function testConvertData() + { + $data = ["token" => "secret-token"]; + $converter = new JsonConverter(); + $this->assertEquals(json_encode($data), $converter->toBody($data)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7c3c4848432856022b1bab588480bda540f12e63 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\Http; + +use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; + +/** + * Class ResponseResolverTest + */ +class ResponseResolverTest extends \PHPUnit\Framework\TestCase +{ + public function testGetResultHandleResponseSuccess() + { + $expectedBody = ['test' => 'testValue']; + $response = new \Zend_Http_Response(201, [], json_encode($expectedBody)); + $responseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $responseHandlerMock->expects($this->once()) + ->method('handleResponse') + ->with($expectedBody) + ->willReturn(true); + $notFoundResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $notFoundResponseHandlerMock->expects($this->never())->method('handleResponse'); + $responseResolver = new ResponseResolver( + new JsonConverter(), + [ + 201 => $responseHandlerMock, + 404 => $notFoundResponseHandlerMock, + ] + ); + $this->assertTrue($responseResolver->getResult($response)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cee3877631c2ed2a795908e48192d4f7cf64e152 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\HTTP\ZendClient; +use Psr\Log\LoggerInterface; +use Magento\Analytics\Model\Connector\NotifyDataChangedCommand; +use Magento\Analytics\Model\Connector\Http\ClientInterface; + +class NotifyDataChangedCommandTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var NotifyDataChangedCommand + */ + private $notifyDataChangedCommand; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var ClientInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $httpClientMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + public $configMock; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + protected function setUp() + { + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $successHandler = $this->getMockBuilder(\Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $successHandler->method('handleResponse') + ->willReturn(true); + + $this->notifyDataChangedCommand = new NotifyDataChangedCommand( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + new ResponseResolver(new JsonConverter(), [201 => $successHandler]), + $this->loggerMock + ); + } + + public function testExecuteSuccess() + { + $configVal = "Config val"; + $token = "Secret token!"; + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($configVal); + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($token); + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + ZendClient::POST, + $configVal, + ['access-token' => $token, 'url' => $configVal] + )->willReturn(new \Zend_Http_Response(201, [])); + $this->assertTrue($this->notifyDataChangedCommand->execute()); + } + + public function testExecuteWithoutToken() + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->httpClientMock->expects($this->never()) + ->method('request'); + $this->assertFalse($this->notifyDataChangedCommand->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8a3f4efb15cf43099e4e2f1f862d9505892deadb --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php @@ -0,0 +1,187 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Connector\Http\ClientInterface; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Analytics\Model\Connector\OTPRequest; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Psr\Log\LoggerInterface; + +/** + * A unit test for testing of the representation of a 'OTP' request. + */ +class OTPRequestTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var OTPRequest + */ + private $subject; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var ClientInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $httpClientMock; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var ResponseResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseResolverMock; + + /** + * @return void + */ + public function setUp() + { + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseResolverMock = $this->getMockBuilder(ResponseResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subject = new OTPRequest( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + $this->responseResolverMock, + $this->loggerMock + ); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + private function getTestData() + { + return [ + 'otp' => 'thisisotp', + 'url' => 'http://www.mystore.com', + 'access-token' => 'thisisaccesstoken', + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + 'body'=> ['access-token' => 'thisisaccesstoken','url' => 'http://www.mystore.com'], + ]; + } + + /** + * @return void + */ + public function testCallSuccess() + { + $data = $this->getTestData(); + + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($data['access-token']); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'] + ) + ->willReturn(new \Zend_Http_Response(201, [])); + $this->responseResolverMock->expects($this->once()) + ->method('getResult') + ->willReturn($data['otp']); + + $this->assertEquals( + $data['otp'], + $this->subject->call() + ); + } + + /** + * @return void + */ + public function testCallNoAccessToken() + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + + $this->httpClientMock->expects($this->never()) + ->method('request'); + + $this->assertFalse($this->subject->call()); + } + + /** + * @return void + */ + public function testCallNoOtp() + { + $data = $this->getTestData(); + + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($data['access-token']); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($data['url']); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'] + ) + ->willReturn(new \Zend_Http_Response(0, [])); + + $this->responseResolverMock->expects($this->once()) + ->method('getResult') + ->willReturn(false); + + $this->loggerMock->expects($this->once()) + ->method('warning'); + + $this->assertFalse($this->subject->call()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/OTPTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/OTPTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0ff36cca5db2dca42386ef1062090a9256c75b83 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/OTPTest.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\Connector\ResponseHandler\OTP; + +/** + * Class OTPTest + */ +class OTPTest extends \PHPUnit\Framework\TestCase +{ + public function testHandleResult() + { + $OTPHandler = new OTP(); + $this->assertFalse($OTPHandler->handleResponse([])); + $expectedOtp = 123; + $this->assertEquals($expectedOtp, $OTPHandler->handleResponse(['otp' => $expectedOtp])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/ReSignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/ReSignUpTest.php new file mode 100644 index 0000000000000000000000000000000000000000..707003149bcfdfafe890295a7b69583eda871760 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/ReSignUpTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp; +use Magento\Analytics\Model\SubscriptionStatusProvider; + +/** + * Class ReSignUpTest + */ +class ReSignUpTest extends \PHPUnit\Framework\TestCase +{ + public function testHandleResult() + { + $analyticsToken = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $analyticsToken->expects($this->once()) + ->method('storeToken') + ->with(null); + $subscriptionHandler = $this->getMockBuilder(SubscriptionHandler::class) + ->disableOriginalConstructor() + ->getMock(); + $subscriptionStatusProvider = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $subscriptionStatusProvider->method('getStatus')->willReturn(SubscriptionStatusProvider::ENABLED); + $reSignUpHandler = new ReSignUp($analyticsToken, $subscriptionHandler, $subscriptionStatusProvider); + $this->assertFalse($reSignUpHandler->handleResponse([])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/SignUpTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/SignUpTest.php new file mode 100644 index 0000000000000000000000000000000000000000..81711cfc569501261cffd36c6d297df5c4c2ad66 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/SignUpTest.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Analytics\Model\Connector\ResponseHandler\SignUp; + +/** + * Class SignUpTest + */ +class SignUpTest extends \PHPUnit\Framework\TestCase +{ + public function testHandleResult() + { + $accessToken = 'access-token-123'; + $analyticsToken = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $analyticsToken->expects($this->once()) + ->method('storeToken') + ->with($accessToken); + $signUpHandler = new SignUp($analyticsToken, new JsonConverter()); + $this->assertFalse($signUpHandler->handleResponse([])); + $this->assertEquals($accessToken, $signUpHandler->handleResponse(['access-token' => $accessToken])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/UpdateTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/UpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7779357e8bea7a0f42cd41fb9550c68b69f34857 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/ResponseHandler/UpdateTest.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector\ResponseHandler; + +use Magento\Analytics\Model\Connector\ResponseHandler\Update; + +/** + * Class UpdateTest + */ +class UpdateTest extends \PHPUnit\Framework\TestCase +{ + public function testHandleResult() + { + $updateHandler = new Update(); + $this->assertTrue($updateHandler->handleResponse([])); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5593496a957b77be52a528b5149babca636596c5 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector; + +use Magento\Analytics\Model\Connector\Http\ClientInterface; +use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Analytics\Model\Connector\SignUpCommand; +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\IntegrationManager; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Integration\Model\Oauth\Token as IntegrationToken; +use Psr\Log\LoggerInterface; + +/** + * Class SignUpCommandTest + */ +class SignUpCommandTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SignUpCommand + */ + private $signUpCommand; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var IntegrationManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $integrationManagerMock; + + /** + * @var IntegrationToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $integrationToken; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var ClientInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $httpClientMock; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var ResponseResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseResolverMock; + + protected function setUp() + { + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationManagerMock = $this->getMockBuilder(IntegrationManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationToken = $this->getMockBuilder(IntegrationToken::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->responseResolverMock = $this->getMockBuilder(ResponseResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->signUpCommand = new SignUpCommand( + $this->analyticsTokenMock, + $this->integrationManagerMock, + $this->configMock, + $this->httpClientMock, + $this->loggerMock, + $this->responseResolverMock + ); + } + + public function testExecuteSuccess() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn($this->integrationToken); + $this->integrationManagerMock->expects($this->once()) + ->method('activateIntegration') + ->willReturn(true); + $data = $this->getTestData(); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($data['url']); + $this->integrationToken->expects($this->any()) + ->method('getData') + ->with('token') + ->willReturn($data['integration-token']); + $httpResponse = new \Zend_Http_Response(201, [], '{"access-token": "' . $data['access-token'] . '"}'); + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + $data['method'], + $data['url'], + $data['body'] + ) + ->willReturn($httpResponse); + $this->responseResolverMock->expects($this->any()) + ->method('getResult') + ->with($httpResponse) + ->willReturn(true); + $this->assertTrue($this->signUpCommand->execute()); + } + + public function testExecuteFailureCannotGenerateToken() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn(false); + $this->integrationManagerMock->expects($this->never()) + ->method('activateIntegration'); + $this->assertFalse($this->signUpCommand->execute()); + } + + public function testExecuteFailureResponseIsEmpty() + { + $this->integrationManagerMock->expects($this->once()) + ->method('generateToken') + ->willReturn($this->integrationToken); + $this->integrationManagerMock->expects($this->once()) + ->method('activateIntegration') + ->willReturn(true); + $httpResponse = new \Zend_Http_Response(0, []); + $this->httpClientMock->expects($this->once()) + ->method('request') + ->willReturn($httpResponse); + $this->responseResolverMock->expects($this->any()) + ->method('getResult') + ->willReturn(false); + $this->assertFalse($this->signUpCommand->execute()); + } + + /** + * Returns test parameters for request. + * + * @return array + */ + private function getTestData() + { + return [ + 'url' => 'http://www.mystore.com', + 'access-token' => 'thisisaccesstoken', + 'integration-token' => 'thisisintegrationtoken', + 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], + 'method' => \Magento\Framework\HTTP\ZendClient::POST, + 'body'=> ['token' => 'thisisintegrationtoken','url' => 'http://www.mystore.com'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..47253a13530e5278e828f1325409dd0197b4de3c --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Connector; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector\Http\ResponseResolver; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\HTTP\ZendClient; +use Psr\Log\LoggerInterface; +use Magento\Analytics\Model\Connector\UpdateCommand; +use Magento\Analytics\Model\Connector\Http\ClientInterface; + +/** + * Class SignUpCommandTest + */ +class UpdateCommandTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var UpdateCommand + */ + private $updateCommand; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var ClientInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $httpClientMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + public $configMock; + + /** + * @var LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var ResponseResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseResolverMock; + + protected function setUp() + { + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClientMock = $this->getMockBuilder(ClientInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->responseResolverMock = $this->getMockBuilder(ResponseResolver::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->updateCommand = new UpdateCommand( + $this->analyticsTokenMock, + $this->httpClientMock, + $this->configMock, + $this->loggerMock, + $this->flagManagerMock, + $this->responseResolverMock + ); + } + + public function testExecuteSuccess() + { + $url = "old.localhost.com"; + $configVal = "Config val"; + $token = "Secret token!"; + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + + $this->configMock->expects($this->any()) + ->method('getValue') + ->willReturn($configVal); + + $this->flagManagerMock->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn($url); + + $this->analyticsTokenMock->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $this->httpClientMock->expects($this->once()) + ->method('request') + ->with( + ZendClient::PUT, + $configVal, + [ + 'url' => $url, + 'new-url' => $configVal, + 'access-token' => $token + ] + )->willReturn(new \Zend_Http_Response(200, [])); + + $this->responseResolverMock->expects($this->once()) + ->method('getResult') + ->willReturn(true); + + $this->assertTrue($this->updateCommand->execute()); + } + + public function testExecuteWithoutToken() + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + + $this->assertFalse($this->updateCommand->execute()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4414b81cbc18324f4e29a390c7b870f9a7ee8554 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ConnectorTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\Connector; +use Magento\Framework\ObjectManagerInterface; +use Magento\Analytics\Model\Connector\SignUpCommand; + +/** + * Class SignUpCommandTest + */ +class ConnectorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var Connector + */ + private $connector; + + /** + * @var SignUpCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $signUpCommandMock; + + /** + * @var array + */ + private $commands; + + protected function setUp() + { + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->signUpCommandMock = $this->getMockBuilder(SignUpCommand::class) + ->disableOriginalConstructor() + ->getMock(); + $this->commands = ['signUp' => SignUpCommand::class]; + $this->connector = new Connector($this->commands, $this->objectManagerMock); + } + + public function testExecute() + { + $commandName = 'signUp'; + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with($this->commands[$commandName]) + ->willReturn($this->signUpCommandMock); + $this->signUpCommandMock->expects($this->once()) + ->method('execute') + ->willReturn(true); + $this->assertTrue($this->connector->execute($commandName)); + } + + /** + * @expectedException \Magento\Framework\Exception\NotFoundException + */ + public function testExecuteCommandNotFound() + { + $commandName = 'register'; + $this->connector->execute($commandName); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a896c309b4007c616bc97d28dc1046c1f64c5605 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/CryptographerTest.php @@ -0,0 +1,226 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Cryptographer; +use Magento\Analytics\Model\EncodedContext; +use Magento\Analytics\Model\EncodedContextFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class CryptographerTest + */ +class CryptographerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var EncodedContextFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $encodedContextFactoryMock; + + /** + * @var EncodedContext|\PHPUnit_Framework_MockObject_MockObject + */ + private $encodedContextMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Cryptographer + */ + private $cryptographer; + + /** + * @var string + */ + private $key; + + /** + * @var array + */ + private $initializationVectors; + + /** + * @var + */ + private $source; + + /** + * @var string + */ + private $cipherMethod = 'AES-256-CBC'; + + /** + * @return void + */ + protected function setUp() + { + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextFactoryMock = $this->getMockBuilder(EncodedContextFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextMock = $this->getMockBuilder(EncodedContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->key = ''; + $this->source = ''; + $this->initializationVectors = []; + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->cryptographer = $this->objectManagerHelper->getObject( + Cryptographer::class, + [ + 'analyticsToken' => $this->analyticsTokenMock, + 'encodedContextFactory' => $this->encodedContextFactoryMock, + 'cipherMethod' => $this->cipherMethod, + ] + ); + } + + /** + * @return void + */ + public function testEncode() + { + $token = 'some-token-value'; + $this->source = 'Some text'; + $this->key = hash('sha256', $token); + + $checkEncodedContext = function ($parameters) { + $emptyRequiredParameters = + array_diff(['content', 'initializationVector'], array_keys(array_filter($parameters))); + if ($emptyRequiredParameters) { + return false; + } + + $encryptedData = openssl_encrypt( + $this->source, + $this->cipherMethod, + $this->key, + OPENSSL_RAW_DATA, + $parameters['initializationVector'] + ); + + return ($encryptedData === $parameters['content']); + }; + + $this->analyticsTokenMock + ->expects($this->once()) + ->method('getToken') + ->with() + ->willReturn($token); + + $this->encodedContextFactoryMock + ->expects($this->once()) + ->method('create') + ->with($this->callback($checkEncodedContext)) + ->willReturn($this->encodedContextMock); + + $this->assertSame($this->encodedContextMock, $this->cryptographer->encode($this->source)); + } + + /** + * @return void + */ + public function testEncodeUniqueInitializationVector() + { + $this->source = 'Some text'; + $token = 'some-token-value'; + + $registerInitializationVector = function ($parameters) { + if (empty($parameters['initializationVector'])) { + return false; + } + + $this->initializationVectors[] = $parameters['initializationVector']; + + return true; + }; + + $this->analyticsTokenMock + ->expects($this->exactly(2)) + ->method('getToken') + ->with() + ->willReturn($token); + + $this->encodedContextFactoryMock + ->expects($this->exactly(2)) + ->method('create') + ->with($this->callback($registerInitializationVector)) + ->willReturn($this->encodedContextMock); + + $this->assertSame($this->encodedContextMock, $this->cryptographer->encode($this->source)); + $this->assertSame($this->encodedContextMock, $this->cryptographer->encode($this->source)); + $this->assertCount(2, array_unique($this->initializationVectors)); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @dataProvider encodeNotValidSourceDataProvider + */ + public function testEncodeNotValidSource($source) + { + $this->cryptographer->encode($source); + } + + /** + * @return array + */ + public function encodeNotValidSourceDataProvider() + { + return [ + 'Array' => [[]], + 'Empty string' => [''], + ]; + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testEncodeNotValidCipherMethod() + { + $source = 'Some string'; + $cryptographer = $this->objectManagerHelper->getObject( + Cryptographer::class, + [ + 'cipherMethod' => 'Wrong-method', + ] + ); + + $cryptographer->encode($source); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testEncodeTokenNotValid() + { + $source = 'Some string'; + + $this->analyticsTokenMock + ->expects($this->once()) + ->method('getToken') + ->with() + ->willReturn(null); + + $this->cryptographer->encode($source); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php b/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a1a7c54510681bac8ea845178207b3dbbc24e73d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/EncodedContextTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\EncodedContext; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class EncodedContextTest + */ +class EncodedContextTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @param string $content + * @param string|null $initializationVector + * @return void + * @dataProvider constructDataProvider + */ + public function testConstruct($content, $initializationVector) + { + $constructorArguments = [ + 'content' => $content, + 'initializationVector' => $initializationVector, + ]; + /** @var EncodedContext $encodedContext */ + $encodedContext = $this->objectManagerHelper->getObject( + EncodedContext::class, + array_filter($constructorArguments) + ); + + $this->assertSame($content, $encodedContext->getContent()); + $this->assertSame($initializationVector ?: '', $encodedContext->getInitializationVector()); + } + + /** + * @return array + */ + public function constructDataProvider() + { + return [ + 'Without Initialization Vector' => ['content text', null], + 'With Initialization Vector' => ['content text', 'c51sd3c4sd68c5sd'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerNotificationTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerNotificationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1582c241bf45d3ea2dbd371ebc1ecd64d80fcae3 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerNotificationTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\Connector; +use Magento\Analytics\Model\ExportDataHandler; +use Magento\Analytics\Model\ExportDataHandlerNotification; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class ExportDataHandlerNotificationTest + */ +class ExportDataHandlerNotificationTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @return void + */ + public function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @return void + */ + public function testThatNotifyExecuted() + { + $expectedResult = true; + $notifyCommandName = 'notifyDataChanged'; + $exportDataHandlerMockObject = $this->createExportDataHandlerMock(); + $analyticsConnectorMockObject = $this->createAnalyticsConnectorMock(); + /** + * @var $exportDataHandlerNotification ExportDataHandlerNotification + */ + $exportDataHandlerNotification = $this->objectManagerHelper->getObject( + ExportDataHandlerNotification::class, + [ + 'exportDataHandler' => $exportDataHandlerMockObject, + 'connector' => $analyticsConnectorMockObject, + ] + ); + $exportDataHandlerMockObject->expects($this->once()) + ->method('prepareExportData') + ->willReturn($expectedResult); + $analyticsConnectorMockObject->expects($this->once()) + ->method('execute') + ->with($notifyCommandName); + $this->assertEquals($expectedResult, $exportDataHandlerNotification->prepareExportData()); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createExportDataHandlerMock() + { + return $this->getMockBuilder(ExportDataHandler::class)->disableOriginalConstructor()->getMock(); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function createAnalyticsConnectorMock() + { + return $this->getMockBuilder(Connector::class)->disableOriginalConstructor()->getMock(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6ffb61d08856749a0cb8db438002f9f17f4adf9d --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ExportDataHandlerTest.php @@ -0,0 +1,270 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\Cryptographer; +use Magento\Analytics\Model\EncodedContext; +use Magento\Analytics\Model\ExportDataHandler; +use Magento\Analytics\Model\FileRecorder; +use Magento\Analytics\Model\ReportWriterInterface; +use Magento\Framework\Archive; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\DirectoryList; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class ExportDataHandlerTest + */ +class ExportDataHandlerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystemMock; + + /** + * @var Archive|\PHPUnit_Framework_MockObject_MockObject + */ + private $archiveMock; + + /** + * @var ReportWriterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $reportWriterMock; + + /** + * @var Cryptographer|\PHPUnit_Framework_MockObject_MockObject + */ + private $cryptographerMock; + + /** + * @var FileRecorder|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileRecorderMock; + + /** + * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $directoryMock; + + /** + * @var EncodedContext|\PHPUnit_Framework_MockObject_MockObject + */ + private $encodedContextMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ExportDataHandler + */ + private $exportDataHandler; + + /** + * @var string + */ + private $subdirectoryPath = 'analytics/'; + + /** + * @var string + */ + private $archiveName = 'data.tgz'; + + /** + * @return void + */ + protected function setUp() + { + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->archiveMock = $this->getMockBuilder(Archive::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->reportWriterMock = $this->getMockBuilder(ReportWriterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cryptographerMock = $this->getMockBuilder(Cryptographer::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileRecorderMock = $this->getMockBuilder(FileRecorder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directoryMock = $this->getMockBuilder(WriteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextMock = $this->getMockBuilder(EncodedContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->exportDataHandler = $this->objectManagerHelper->getObject( + ExportDataHandler::class, + [ + 'filesystem' => $this->filesystemMock, + 'archive' => $this->archiveMock, + 'reportWriter' => $this->reportWriterMock, + 'cryptographer' => $this->cryptographerMock, + 'fileRecorder' => $this->fileRecorderMock, + 'subdirectoryPath' => $this->subdirectoryPath, + 'archiveName' => $this->archiveName, + ] + ); + } + + /** + * @param bool $isArchiveSourceDirectory + * @dataProvider prepareExportDataDataProvider + */ + public function testPrepareExportData($isArchiveSourceDirectory) + { + $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/'; + $archiveRelativePath = $this->subdirectoryPath . $this->archiveName; + + $archiveSource = $isArchiveSourceDirectory ? (__DIR__) : '/tmp/' . $tmpFilesDirectoryPath; + $archiveAbsolutePath = '/tmp/' . $archiveRelativePath; + + $this->filesystemMock + ->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::SYS_TMP) + ->willReturn($this->directoryMock); + $this->directoryMock + ->expects($this->exactly(4)) + ->method('delete') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$archiveRelativePath] + ); + + $this->directoryMock + ->expects($this->exactly(4)) + ->method('getAbsolutePath') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$tmpFilesDirectoryPath], + [$archiveRelativePath], + [$archiveRelativePath] + ) + ->willReturnOnConsecutiveCalls( + $archiveSource, + $archiveSource, + $archiveAbsolutePath, + $archiveAbsolutePath + ); + + $this->reportWriterMock + ->expects($this->once()) + ->method('write') + ->with($this->directoryMock, $tmpFilesDirectoryPath); + + $this->directoryMock + ->expects($this->exactly(2)) + ->method('isExist') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$archiveRelativePath] + ) + ->willReturnOnConsecutiveCalls( + true, + true + ); + + $this->directoryMock + ->expects($this->once()) + ->method('create') + ->with(dirname($archiveRelativePath)); + + $this->archiveMock + ->expects($this->once()) + ->method('pack') + ->with( + $archiveSource, + $archiveAbsolutePath, + $isArchiveSourceDirectory ? true : false + ); + + $fileContent = 'Some text'; + $this->directoryMock + ->expects($this->once()) + ->method('readFile') + ->with($archiveRelativePath) + ->willReturn($fileContent); + + $this->cryptographerMock + ->expects($this->once()) + ->method('encode') + ->with($fileContent) + ->willReturn($this->encodedContextMock); + + $this->fileRecorderMock + ->expects($this->once()) + ->method('recordNewFile') + ->with($this->encodedContextMock); + + $this->assertTrue($this->exportDataHandler->prepareExportData()); + } + + /** + * @return array + */ + public function prepareExportDataDataProvider() + { + return [ + 'Data source for archive is directory' => [true], + 'Data source for archive doesn\'t directory' => [false], + ]; + } + + /** + * @return void + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testPrepareExportDataWithLocalizedException() + { + $tmpFilesDirectoryPath = $this->subdirectoryPath . 'tmp/'; + $archivePath = $this->subdirectoryPath . $this->archiveName; + + $this->filesystemMock + ->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::SYS_TMP) + ->willReturn($this->directoryMock); + $this->reportWriterMock + ->expects($this->once()) + ->method('write') + ->with($this->directoryMock, $tmpFilesDirectoryPath); + $this->directoryMock + ->expects($this->exactly(3)) + ->method('delete') + ->withConsecutive( + [$tmpFilesDirectoryPath], + [$tmpFilesDirectoryPath], + [$archivePath] + ); + $this->directoryMock + ->expects($this->exactly(2)) + ->method('getAbsolutePath') + ->with($tmpFilesDirectoryPath); + $this->directoryMock + ->expects($this->once()) + ->method('isExist') + ->with($tmpFilesDirectoryPath) + ->willReturn(false); + + $this->assertNull($this->exportDataHandler->prepareExportData()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..da5f6af3ca4e1d153ae9c093b3497ba3becf0a16 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoManagerTest.php @@ -0,0 +1,194 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\FileInfo; +use Magento\Analytics\Model\FileInfoFactory; +use Magento\Analytics\Model\FileInfoManager; +use Magento\Framework\FlagManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class FileInfoManagerTest + */ +class FileInfoManagerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var FileInfoFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoFactoryMock; + + /** + * @var FileInfo|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var FileInfoManager + */ + private $fileInfoManager; + + /** + * @var string + */ + private $flagCode = 'analytics_file_info'; + + /** + * @var array + */ + private $encodedParameters = [ + 'initializationVector' + ]; + + /** + * @return void + */ + protected function setUp() + { + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoFactoryMock = $this->getMockBuilder(FileInfoFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoMock = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->fileInfoManager = $this->objectManagerHelper->getObject( + FileInfoManager::class, + [ + 'flagManager' => $this->flagManagerMock, + 'fileInfoFactory' => $this->fileInfoFactoryMock, + 'flagCode' => $this->flagCode, + 'encodedParameters' => $this->encodedParameters, + ] + ); + } + + /** + * @return void + */ + public function testSave() + { + $path = 'path/to/file'; + $initializationVector = openssl_random_pseudo_bytes(16); + $parameters = [ + 'path' => $path, + 'initializationVector' => $initializationVector, + ]; + + $this->fileInfoMock + ->expects($this->once()) + ->method('getPath') + ->with() + ->willReturn($path); + $this->fileInfoMock + ->expects($this->once()) + ->method('getInitializationVector') + ->with() + ->willReturn($initializationVector); + + foreach ($this->encodedParameters as $encodedParameter) { + $parameters[$encodedParameter] = base64_encode($parameters[$encodedParameter]); + } + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with($this->flagCode, $parameters); + + $this->assertTrue($this->fileInfoManager->save($this->fileInfoMock)); + } + + /** + * @param string|null $path + * @param string|null $initializationVector + * @dataProvider saveWithLocalizedExceptionDataProvider + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testSaveWithLocalizedException($path, $initializationVector) + { + $this->fileInfoMock + ->expects($this->once()) + ->method('getPath') + ->with() + ->willReturn($path); + $this->fileInfoMock + ->expects($this->once()) + ->method('getInitializationVector') + ->with() + ->willReturn($initializationVector); + + $this->fileInfoManager->save($this->fileInfoMock); + } + + /** + * @return array + */ + public function saveWithLocalizedExceptionDataProvider() + { + return [ + 'Empty FileInfo' => [null, null], + 'FileInfo without IV' => ['path/to/file', null], + ]; + } + + /** + * @dataProvider loadDataProvider + * @param array|null $parameters + */ + public function testLoad($parameters) + { + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with($this->flagCode) + ->willReturn($parameters); + + $processedParameters = $parameters ?: []; + $encodedParameters = array_intersect($this->encodedParameters, array_keys($processedParameters)); + foreach ($encodedParameters as $encodedParameter) { + $processedParameters[$encodedParameter] = base64_decode($processedParameters[$encodedParameter]); + } + + $this->fileInfoFactoryMock + ->expects($this->once()) + ->method('create') + ->with($processedParameters) + ->willReturn($this->fileInfoMock); + + $this->assertSame($this->fileInfoMock, $this->fileInfoManager->load()); + } + + /** + * @return array + */ + public function loadDataProvider() + { + return [ + 'Empty flag data' => [null], + 'Correct flag data' => [[ + 'path' => 'path/to/file', + 'initializationVector' => 'xUJjl54MVke+FvMFSBpRSA==', + ]], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php new file mode 100644 index 0000000000000000000000000000000000000000..43ce833f1f03f7b26c340b2b60d0f42291916390 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileInfoTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\FileInfo; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class FileInfoTest + */ +class FileInfoTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @param string|null $path + * @param string|null $initializationVector + * @return void + * @dataProvider constructDataProvider + */ + public function testConstruct($path, $initializationVector) + { + $constructorArguments = [ + 'path' => $path, + 'initializationVector' => $initializationVector, + ]; + /** @var FileInfo $fileInfo */ + $fileInfo = $this->objectManagerHelper->getObject( + FileInfo::class, + array_filter($constructorArguments) + ); + + $this->assertSame($path ?: '', $fileInfo->getPath()); + $this->assertSame($initializationVector ?: '', $fileInfo->getInitializationVector()); + } + + /** + * @return array + */ + public function constructDataProvider() + { + return [ + 'Degenerate object' => [null, null], + 'Without Initialization Vector' => ['content text', null], + 'With Initialization Vector' => ['content text', 'c51sd3c4sd68c5sd'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3c9520bdd995b2b552a9ab49b33158bda5b85638 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/FileRecorderTest.php @@ -0,0 +1,209 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\EncodedContext; +use Magento\Analytics\Model\FileInfo; +use Magento\Analytics\Model\FileInfoFactory; +use Magento\Analytics\Model\FileInfoManager; +use Magento\Analytics\Model\FileRecorder; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class FileRecorderTest + */ +class FileRecorderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var FileInfoManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoManagerMock; + + /** + * @var FileInfoFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoFactoryMock; + + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystemMock; + + /** + * @var FileInfo|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoMock; + + /** + * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $directoryMock; + + /** + * @var EncodedContext|\PHPUnit_Framework_MockObject_MockObject + */ + private $encodedContextMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var FileRecorder + */ + private $fileRecorder; + + /** + * @var string + */ + private $fileSubdirectoryPath = 'analytics_subdir/'; + + /** + * @var string + */ + private $encodedFileName = 'filename.tgz'; + + /** + * @return void + */ + protected function setUp() + { + $this->fileInfoManagerMock = $this->getMockBuilder(FileInfoManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoFactoryMock = $this->getMockBuilder(FileInfoFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $this->filesystemMock = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileInfoMock = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directoryMock = $this->getMockBuilder(WriteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->encodedContextMock = $this->getMockBuilder(EncodedContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->fileRecorder = $this->objectManagerHelper->getObject( + FileRecorder::class, + [ + 'fileInfoManager' => $this->fileInfoManagerMock, + 'fileInfoFactory' => $this->fileInfoFactoryMock, + 'filesystem' => $this->filesystemMock, + 'fileSubdirectoryPath' => $this->fileSubdirectoryPath, + 'encodedFileName' => $this->encodedFileName, + ] + ); + } + + /** + * @param string $pathToExistingFile + * @dataProvider recordNewFileDataProvider + */ + public function testRecordNewFile($pathToExistingFile) + { + $content = openssl_random_pseudo_bytes(200); + + $this->filesystemMock + ->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($this->directoryMock); + + $this->encodedContextMock + ->expects($this->once()) + ->method('getContent') + ->with() + ->willReturn($content); + + $hashLength = 64; + $fileRelativePathPattern = '#' . preg_quote($this->fileSubdirectoryPath, '#') + . '.{' . $hashLength . '}/' . preg_quote($this->encodedFileName, '#') . '#'; + $this->directoryMock + ->expects($this->once()) + ->method('writeFile') + ->with($this->matchesRegularExpression($fileRelativePathPattern), $content) + ->willReturn($this->directoryMock); + + $this->fileInfoManagerMock + ->expects($this->once()) + ->method('load') + ->with() + ->willReturn($this->fileInfoMock); + + $this->encodedContextMock + ->expects($this->once()) + ->method('getInitializationVector') + ->with() + ->willReturn('init_vector***'); + + /** register file */ + $this->fileInfoFactoryMock + ->expects($this->once()) + ->method('create') + ->with($this->callback( + function ($parameters) { + return !empty($parameters['path']) && ('init_vector***' === $parameters['initializationVector']); + } + )) + ->willReturn($this->fileInfoMock); + $this->fileInfoManagerMock + ->expects($this->once()) + ->method('save') + ->with($this->fileInfoMock); + + /** remove old file */ + $this->fileInfoMock + ->expects($this->exactly($pathToExistingFile ? 3 : 1)) + ->method('getPath') + ->with() + ->willReturn($pathToExistingFile); + $directoryName = dirname($pathToExistingFile); + if ($directoryName === '.') { + $this->directoryMock + ->expects($this->once()) + ->method('delete') + ->with($pathToExistingFile); + } elseif ($directoryName) { + $this->directoryMock + ->expects($this->exactly(2)) + ->method('delete') + ->withConsecutive( + [$pathToExistingFile], + [$directoryName] + ); + } + + $this->assertTrue($this->fileRecorder->recordNewFile($this->encodedContextMock)); + } + + /** + * @return array + */ + public function recordNewFileDataProvider() + { + return [ + 'File doesn\'t exist' => [''], + 'Existing file into subdirectory' => ['dir_name/file.txt'], + 'Existing file doesn\'t into subdirectory' => ['file.txt'], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php b/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3076a22c85be4c34e75e8a2113e1c6aa3fe66dde --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/IntegrationManagerTest.php @@ -0,0 +1,228 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Integration\Api\IntegrationServiceInterface; +use Magento\Config\Model\Config; +use Magento\Integration\Model\Integration; +use Magento\Analytics\Model\IntegrationManager; +use Magento\Integration\Api\OauthServiceInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class IntegrationManagerTest + */ +class IntegrationManagerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var IntegrationServiceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $integrationServiceMock; + + /** + * @var OauthServiceInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $oauthServiceMock; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var Integration|\PHPUnit_Framework_MockObject_MockObject + */ + private $integrationMock; + + /** + * @var IntegrationManager + */ + private $integrationManager; + + public function setUp() + { + $objectManagerHelper = new ObjectManagerHelper($this); + $this->integrationServiceMock = $this->getMockBuilder(IntegrationServiceInterface::class) + ->getMock(); + $this->configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + $this->oauthServiceMock = $this->getMockBuilder(OauthServiceInterface::class) + ->getMock(); + $this->integrationMock = $this->getMockBuilder(Integration::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getId', + 'getConsumerId' + ]) + ->getMock(); + $this->integrationManager = $objectManagerHelper->getObject( + IntegrationManager::class, + [ + 'integrationService' => $this->integrationServiceMock, + 'oauthService' => $this->oauthServiceMock, + 'config' => $this->configMock + ] + ); + } + + /** + * @param string $status + * + * @return array + */ + private function getIntegrationUserData($status) + { + return [ + 'name' => 'ma-integration-user', + 'status' => $status, + 'all_resources' => false, + 'resource' => [ + 'Magento_Analytics::analytics', + 'Magento_Analytics::analytics_api' + ], + ]; + } + + /** + * @return void + */ + public function testActivateIntegrationSuccess() + { + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->exactly(2)) + ->method('getId') + ->willReturn(100500); + $integrationData = $this->getIntegrationUserData(Integration::STATUS_ACTIVE); + $integrationData['integration_id'] = 100500; + $this->configMock->expects($this->exactly(2)) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->once()) + ->method('update') + ->with($integrationData); + $this->assertTrue($this->integrationManager->activateIntegration()); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ + public function testActivateIntegrationFailureNoSuchEntity() + { + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->once()) + ->method('getId') + ->willReturn(null); + $this->configMock->expects($this->once()) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->never()) + ->method('update'); + $this->integrationManager->activateIntegration(); + } + + /** + * @dataProvider integrationIdDataProvider + * + * @param int|null $integrationId If null integration is absent. + * @return void + */ + public function testGetTokenNewIntegration($integrationId) + { + $this->configMock->expects($this->atLeastOnce()) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->once()) + ->method('getConsumerId') + ->willReturn(100500); + $this->integrationMock->expects($this->once()) + ->method('getId') + ->willReturn($integrationId); + if (!$integrationId) { + $this->integrationServiceMock + ->expects($this->once()) + ->method('create') + ->with($this->getIntegrationUserData(Integration::STATUS_INACTIVE)) + ->willReturn($this->integrationMock); + } + $this->oauthServiceMock->expects($this->at(0)) + ->method('getAccessToken') + ->with(100500) + ->willReturn(false); + $this->oauthServiceMock->expects($this->at(2)) + ->method('getAccessToken') + ->with(100500) + ->willReturn('IntegrationToken'); + $this->oauthServiceMock->expects($this->once()) + ->method('createAccessToken') + ->with(100500, true) + ->willReturn(true); + $this->assertEquals('IntegrationToken', $this->integrationManager->generateToken()); + } + + /** + * @dataProvider integrationIdDataProvider + * + * @param int|null $integrationId If null integration is absent. + * @return void + */ + public function testGetTokenExistingIntegration($integrationId) + { + $this->configMock->expects($this->atLeastOnce()) + ->method('getConfigDataValue') + ->with('analytics/integration_name', null, null) + ->willReturn('ma-integration-user'); + $this->integrationServiceMock->expects($this->once()) + ->method('findByName') + ->with('ma-integration-user') + ->willReturn($this->integrationMock); + $this->integrationMock->expects($this->once()) + ->method('getConsumerId') + ->willReturn(100500); + $this->integrationMock->expects($this->once()) + ->method('getId') + ->willReturn($integrationId); + if (!$integrationId) { + $this->integrationServiceMock + ->expects($this->once()) + ->method('create') + ->with($this->getIntegrationUserData(Integration::STATUS_INACTIVE)) + ->willReturn($this->integrationMock); + } + $this->oauthServiceMock->expects($this->once()) + ->method('getAccessToken') + ->with(100500) + ->willReturn('IntegrationToken'); + $this->oauthServiceMock->expects($this->never()) + ->method('createAccessToken'); + $this->assertEquals('IntegrationToken', $this->integrationManager->generateToken()); + } + + /** + * @return array + */ + public function integrationIdDataProvider() + { + return [ + [1], + [null], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c7aa2219d1eee6f5304359db0b67d5be87c0d459 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/LinkProviderTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Api\Data\LinkInterface; +use Magento\Analytics\Api\Data\LinkInterfaceFactory; +use Magento\Analytics\Model\FileInfo; +use Magento\Analytics\Model\FileInfoManager; +use Magento\Analytics\Model\LinkProvider; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class LinkProviderTest + */ +class LinkProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var LinkInterfaceFactory | \PHPUnit_Framework_MockObject_MockObject + */ + private $linkInterfaceFactoryMock; + + /** + * @var FileInfoManager | \PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoManagerMock; + + /** + * @var StoreManagerInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerInterfaceMock; + + /** + * @var LinkInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $linkInterfaceMock; + + /** + * @var FileInfo | \PHPUnit_Framework_MockObject_MockObject + */ + private $fileInfoMock; + + /** + * @var Store | \PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var LinkProvider + */ + private $linkProvider; + + /** + * @return void + */ + protected function setUp() + { + $this->linkInterfaceFactoryMock = $this->getMockBuilder(LinkInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->fileInfoManagerMock = $this->getMockBuilder(FileInfoManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManagerInterfaceMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + $this->linkInterfaceMock = $this->getMockBuilder(LinkInterface::class) + ->getMockForAbstractClass(); + $this->fileInfoMock = $this->getMockBuilder(FileInfo::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeMock = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->linkProvider = $this->objectManagerHelper->getObject( + LinkProvider::class, + [ + 'linkFactory' => $this->linkInterfaceFactoryMock, + 'fileInfoManager' => $this->fileInfoManagerMock, + 'storeManager' => $this->storeManagerInterfaceMock + ] + ); + } + + public function testGet() + { + $baseUrl = 'http://magento.local/pub/media/'; + $fileInfoPath = 'analytics/data.tgz'; + $fileInitializationVector = 'er312esq23eqq'; + $this->fileInfoManagerMock->expects($this->once()) + ->method('load') + ->willReturn($this->fileInfoMock); + $this->linkInterfaceFactoryMock->expects($this->once()) + ->method('create') + ->with( + [ + 'initializationVector' => base64_encode($fileInitializationVector), + 'url' => $baseUrl . $fileInfoPath + ] + ) + ->willReturn($this->linkInterfaceMock); + $this->storeManagerInterfaceMock->expects($this->once()) + ->method('getStore')->willReturn($this->storeMock); + $this->storeMock->expects($this->once()) + ->method('getBaseUrl') + ->with( + UrlInterface::URL_TYPE_MEDIA + ) + ->willReturn($baseUrl); + $this->fileInfoMock->expects($this->atLeastOnce()) + ->method('getPath') + ->willReturn($fileInfoPath); + $this->fileInfoMock->expects($this->atLeastOnce()) + ->method('getInitializationVector') + ->willReturn($fileInitializationVector); + $this->assertEquals($this->linkInterfaceMock, $this->linkProvider->get()); + } + + /** + * @param string|null $fileInfoPath + * @param string|null $fileInitializationVector + * + * @dataProvider fileNotReadyDataProvider + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + * @expectedExceptionMessage File is not ready yet. + */ + public function testFileNotReady($fileInfoPath, $fileInitializationVector) + { + $this->fileInfoManagerMock->expects($this->once()) + ->method('load') + ->willReturn($this->fileInfoMock); + $this->fileInfoMock->expects($this->once()) + ->method('getPath') + ->willReturn($fileInfoPath); + $this->fileInfoMock->expects($this->any()) + ->method('getInitializationVector') + ->willReturn($fileInitializationVector); + $this->linkProvider->get(); + } + + /** + * @return array + */ + public function fileNotReadyDataProvider() + { + return [ + [null, 'initVector'], + ['path', null], + ['', 'initVector'], + ['path', ''], + ['', ''], + [null, null] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a89e06562383b03a314622e6f9a05817a53b0d69 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/Plugin/BaseUrlConfigPluginTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\Plugin; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Plugin\BaseUrlConfigPlugin; +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Config\Value; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; + +/** + * Class BaseUrlConfigPluginTest + */ +class BaseUrlConfigPluginTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SubscriptionUpdateHandler | \PHPUnit_Framework_MockObject_MockObject + */ + private $subscriptionUpdateHandlerMock; + + /** + * @var Value | \PHPUnit_Framework_MockObject_MockObject + */ + private $configValueMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var BaseUrlConfigPlugin + */ + private $plugin; + + /** + * @return void + */ + protected function setUp() + { + $this->subscriptionUpdateHandlerMock = $this->getMockBuilder(SubscriptionUpdateHandler::class) + ->disableOriginalConstructor() + ->getMock(); + $this->configValueMock = $this->getMockBuilder(Value::class) + ->disableOriginalConstructor() + ->setMethods(['isValueChanged', 'getPath', 'getScope', 'getOldValue']) + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->plugin = $this->objectManagerHelper->getObject( + BaseUrlConfigPlugin::class, + [ + 'subscriptionUpdateHandler' => $this->subscriptionUpdateHandlerMock, + ] + ); + } + + /** + * @param array $configValueData + * @return void + * @dataProvider afterSavePluginIsNotApplicableDataProvider + */ + public function testAfterSavePluginIsNotApplicable( + array $configValueData + ) { + $this->configValueMock + ->method('isValueChanged') + ->willReturn($configValueData['isValueChanged']); + $this->configValueMock + ->method('getPath') + ->willReturn($configValueData['path']); + $this->configValueMock + ->method('getScope') + ->willReturn($configValueData['scope']); + $this->subscriptionUpdateHandlerMock + ->expects($this->never()) + ->method('processUrlUpdate'); + + $this->assertEquals( + $this->configValueMock, + $this->plugin->afterAfterSave($this->configValueMock, $this->configValueMock) + ); + } + + /** + * @return array + */ + public function afterSavePluginIsNotApplicableDataProvider() + { + return [ + 'Value has not been changed' => [ + 'Config Value Data' => [ + 'isValueChanged' => false, + 'path' => Store::XML_PATH_SECURE_BASE_URL, + 'scope' => ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ], + ], + 'Unsecure URL has been changed' => [ + 'Config Value Data' => [ + 'isValueChanged' => true, + 'path' => Store::XML_PATH_UNSECURE_BASE_URL, + 'scope' => ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ], + ], + 'Secure URL has been changed not in the Default scope' => [ + 'Config Value Data' => [ + 'isValueChanged' => true, + 'path' => Store::XML_PATH_SECURE_BASE_URL, + 'scope' => ScopeInterface::SCOPE_STORES + ], + ], + ]; + } + + /** + * @return void + */ + public function testAfterSavePluginIsApplicable() + { + $this->configValueMock + ->method('isValueChanged') + ->willReturn(true); + $this->configValueMock + ->method('getPath') + ->willReturn(Store::XML_PATH_SECURE_BASE_URL); + $this->configValueMock + ->method('getScope') + ->willReturn(ScopeConfigInterface::SCOPE_TYPE_DEFAULT); + $this->configValueMock + ->method('getOldValue') + ->willReturn('http://store.com'); + $this->subscriptionUpdateHandlerMock + ->expects($this->once()) + ->method('processUrlUpdate') + ->with('http://store.com'); + + $this->assertEquals( + $this->configValueMock, + $this->plugin->afterAfterSave($this->configValueMock, $this->configValueMock) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0607a977e5b68734ff264f587d887183b498e241 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportUrlProviderTest.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector\OTPRequest; +use Magento\Analytics\Model\Exception\State\SubscriptionUpdateException; +use Magento\Analytics\Model\ReportUrlProvider; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ReportUrlProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var OTPRequest|\PHPUnit_Framework_MockObject_MockObject + */ + private $otpRequestMock; + + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ReportUrlProvider + */ + private $reportUrlProvider; + + /** + * @var string + */ + private $urlReportConfigPath = 'path/url/report'; + + /** + * @return void + */ + protected function setUp() + { + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->otpRequestMock = $this->getMockBuilder(OTPRequest::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportUrlProvider = $this->objectManagerHelper->getObject( + ReportUrlProvider::class, + [ + 'config' => $this->configMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'otpRequest' => $this->otpRequestMock, + 'flagManager' => $this->flagManagerMock, + 'urlReportConfigPath' => $this->urlReportConfigPath, + ] + ); + } + + /** + * @param bool $isTokenExist + * @param string|null $otp If null OTP was not received. + * + * @dataProvider getUrlDataProvider + */ + public function testGetUrl($isTokenExist, $otp) + { + $reportUrl = 'https://example.com/report'; + $url = ''; + + $this->configMock + ->expects($this->once()) + ->method('getValue') + ->with($this->urlReportConfigPath) + ->willReturn($reportUrl); + $this->analyticsTokenMock + ->expects($this->once()) + ->method('isTokenExist') + ->with() + ->willReturn($isTokenExist); + $this->otpRequestMock + ->expects($isTokenExist ? $this->once() : $this->never()) + ->method('call') + ->with() + ->willReturn($otp); + if ($isTokenExist && $otp) { + $url = $reportUrl . '?' . http_build_query(['otp' => $otp], '', '&'); + } + $this->assertSame($url ?: $reportUrl, $this->reportUrlProvider->getUrl()); + } + + /** + * @return array + */ + public function getUrlDataProvider() + { + return [ + 'TokenDoesNotExist' => [false, null], + 'TokenExistAndOtpEmpty' => [true, null], + 'TokenExistAndOtpValid' => [true, '249e6b658877bde2a77bc4ab'], + ]; + } + + /** + * @return void + */ + public function testGetUrlWhenSubscriptionUpdateRunning() + { + $this->flagManagerMock + ->expects($this->once()) + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn('http://store.com'); + $this->expectException(SubscriptionUpdateException::class); + $this->reportUrlProvider->getUrl(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d9b030b84d6394817815645b40501ea6759ce725 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportWriterTest.php @@ -0,0 +1,213 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\ConfigInterface; +use Magento\Analytics\Model\ProviderFactory; +use Magento\Analytics\Model\ReportWriter; +use Magento\Analytics\ReportXml\DB\ReportValidator; +use Magento\Analytics\ReportXml\ReportProvider; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ReportWriterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configInterfaceMock; + + /** + * @var ReportValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $reportValidatorMock; + + /** + * @var ProviderFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $providerFactoryMock; + + /** + * @var ReportProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $reportProviderMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $directoryMock; + + /** + * @var ReportWriter + */ + private $reportWriter; + + /** + * @var string + */ + private $reportName = 'testReport'; + + /** + * @var string + */ + private $providerName = 'testProvider'; + + /** + * @var string + */ + private $providerClass = 'Magento\Analytics\Provider'; + + /** + * @return void + */ + protected function setUp() + { + $this->configInterfaceMock = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); + $this->reportValidatorMock = $this->getMockBuilder(ReportValidator::class) + ->disableOriginalConstructor()->getMock(); + $this->providerFactoryMock = $this->getMockBuilder(ProviderFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->reportProviderMock = $this->getMockBuilder(ReportProvider::class) + ->disableOriginalConstructor()->getMock(); + $this->directoryMock = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportWriter = $this->objectManagerHelper->getObject( + ReportWriter::class, + [ + 'config' => $this->configInterfaceMock, + 'reportValidator' => $this->reportValidatorMock, + 'providerFactory' => $this->providerFactoryMock + ] + ); + } + + /** + * @param array $configData + * @return void + * + * @dataProvider configDataProvider + */ + public function testWrite(array $configData) + { + $errors = []; + $fileData = [ + ['number' => 1, 'type' => 'Shoes Usual'] + ]; + $this->configInterfaceMock + ->expects($this->once()) + ->method('get') + ->with() + ->willReturn([$configData]); + $this->providerFactoryMock + ->expects($this->once()) + ->method('create') + ->with($this->providerClass) + ->willReturn($this->reportProviderMock); + $parameterName = isset(reset($configData)[0]['parameters']['name']) + ? reset($configData)[0]['parameters']['name'] + : ''; + $this->reportProviderMock->expects($this->once()) + ->method('getReport') + ->with($parameterName ?: null) + ->willReturn($fileData); + $errorStreamMock = $this->getMockBuilder( + \Magento\Framework\Filesystem\File\WriteInterface::class + )->getMockForAbstractClass(); + $errorStreamMock + ->expects($this->once()) + ->method('lock') + ->with(); + $errorStreamMock + ->expects($this->exactly(2)) + ->method('writeCsv') + ->withConsecutive( + [array_keys($fileData[0])], + [$fileData[0]] + ); + $errorStreamMock->expects($this->once())->method('unlock'); + $errorStreamMock->expects($this->once())->method('close'); + if ($parameterName) { + $this->reportValidatorMock + ->expects($this->once()) + ->method('validate') + ->with($parameterName) + ->willReturn($errors); + } + $this->directoryMock + ->expects($this->once()) + ->method('openFile') + ->with( + $this->stringContains('/var/tmp' . $parameterName ?: $this->reportName), + 'w+' + )->willReturn($errorStreamMock); + $this->assertTrue($this->reportWriter->write($this->directoryMock, '/var/tmp')); + } + + /** + * @param array $configData + * @return void + * + * @dataProvider configDataProvider + */ + public function testWriteErrorFile($configData) + { + $errors = ['orders', 'SQL Error: test']; + $this->configInterfaceMock->expects($this->once())->method('get')->willReturn([$configData]); + $errorStreamMock = $this->getMockBuilder( + \Magento\Framework\Filesystem\File\WriteInterface::class + )->getMockForAbstractClass(); + $errorStreamMock->expects($this->once())->method('lock'); + $errorStreamMock->expects($this->once())->method('writeCsv')->with($errors); + $errorStreamMock->expects($this->once())->method('unlock'); + $errorStreamMock->expects($this->once())->method('close'); + $this->reportValidatorMock->expects($this->once())->method('validate')->willReturn($errors); + $this->directoryMock->expects($this->once())->method('openFile')->with('/var/tmp' . 'errors.csv', 'w+') + ->willReturn($errorStreamMock); + $this->assertTrue($this->reportWriter->write($this->directoryMock, '/var/tmp')); + } + + /** + * @return void + */ + public function testWriteEmptyReports() + { + $this->configInterfaceMock->expects($this->once())->method('get')->willReturn([]); + $this->reportValidatorMock->expects($this->never())->method('validate'); + $this->directoryMock->expects($this->never())->method('openFile'); + $this->assertTrue($this->reportWriter->write($this->directoryMock, '/var/tmp')); + } + + /** + * @return array + */ + public function configDataProvider() + { + return [ + 'reportProvider' => [ + [ + 'providers' => [ + [ + 'name' => $this->providerName, + 'class' => $this->providerClass, + 'parameters' => [ + 'name' => $this->reportName + ], + ] + ] + ] + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f314d77f32b41b66bb6fd6503985dd6406d7e2c6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/ReportXml/ModuleIteratorTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Model\ReportXml; + +use Magento\Analytics\Model\ReportXml\ModuleIterator; +use Magento\Framework\Module\Manager as ModuleManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class ModuleIteratorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ModuleManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $moduleManagerMock; + + /** + * @var ModuleIterator|\PHPUnit_Framework_MockObject_MockObject + */ + private $moduleIterator; + + public function setUp() + { + $this->moduleManagerMock = $this->getMockBuilder(ModuleManager::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->moduleIterator = $objectManagerHelper->getObject( + ModuleIterator::class, + [ + 'moduleManager' => $this->moduleManagerMock, + 'iterator' => new \ArrayIterator([0 => ['module_name' => 'Coco_Module']]) + ] + ); + } + + public function testCurrent() + { + $this->moduleManagerMock->expects($this->once()) + ->method('isEnabled') + ->with('Coco_Module') + ->willReturn(true); + foreach ($this->moduleIterator as $item) { + $this->assertEquals(['module_name' => 'Coco_Module', 'status' => 'Enabled'], $item); + } + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cc46d175543add0ee2f55bc416ec8aeb1b2a83eb --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/StoreConfigurationProviderTest.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\StoreConfigurationProvider; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\StoreManagerInterface; + +class StoreConfigurationProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var string[] + */ + private $configPaths; + + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var WebsiteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $websiteMock; + + /** + * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var StoreConfigurationProvider|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeConfigurationProvider; + + /** + * @return void + */ + protected function setUp() + { + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->websiteMock = $this->getMockBuilder(WebsiteInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->storeMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->configPaths = [ + 'web/unsecure/base_url', + 'currency/options/base', + 'general/locale/timezone' + ]; + + $this->storeConfigurationProvider = new StoreConfigurationProvider( + $this->scopeConfigMock, + $this->storeManagerMock, + $this->configPaths + ); + } + + public function testGetReport() + { + $map = [ + ['web/unsecure/base_url', 'default', 0, '127.0.0.1'], + ['currency/options/base', 'default', 0, 'USD'], + ['general/locale/timezone', 'default', 0, 'America/Dawson'], + ['web/unsecure/base_url', 'websites', 1, '127.0.0.2'], + ['currency/options/base', 'websites', 1, 'USD'], + ['general/locale/timezone', 'websites', 1, 'America/Belem'], + ['web/unsecure/base_url', 'stores', 2, '127.0.0.3'], + ['currency/options/base', 'stores', 2, 'USD'], + ['general/locale/timezone', 'stores', 2, 'America/Phoenix'], + ]; + + $this->scopeConfigMock + ->method('getValue') + ->will($this->returnValueMap($map)); + + $this->storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->willReturn([$this->websiteMock]); + + $this->storeManagerMock->expects($this->once()) + ->method('getStores') + ->willReturn([$this->storeMock]); + + $this->websiteMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->storeMock->expects($this->once()) + ->method('getId') + ->willReturn(2); + $result = iterator_to_array($this->storeConfigurationProvider->getReport()); + $resultValues = []; + foreach ($result as $item) { + $resultValues[] = array_values($item); + } + array_multisort($resultValues); + array_multisort($map); + $this->assertEquals($resultValues, $map); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d6b041ce03178c96e96c9cd664e366030b0e7444 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/SubscriptionStatusProviderTest.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model; + +use Magento\Analytics\Model\AnalyticsToken; +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class SubscriptionStatusProviderTest. + */ +class SubscriptionStatusProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var AnalyticsToken|\PHPUnit_Framework_MockObject_MockObject + */ + private $analyticsTokenMock; + + /** + * @var FlagManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $flagManagerMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var SubscriptionStatusProvider + */ + private $statusProvider; + + /** + * @return void + */ + protected function setUp() + { + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->getMockForAbstractClass(); + + $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->flagManagerMock = $this->getMockBuilder(FlagManager::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->statusProvider = $this->objectManagerHelper->getObject( + SubscriptionStatusProvider::class, + [ + 'scopeConfig' => $this->scopeConfigMock, + 'analyticsToken' => $this->analyticsTokenMock, + 'flagManager' => $this->flagManagerMock, + ] + ); + } + + /** + * @param array $flagManagerData + * @dataProvider getStatusShouldBeFailedDataProvider + */ + public function testGetStatusShouldBeFailed(array $flagManagerData) + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(false); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(true); + + $this->expectFlagManagerReturn($flagManagerData); + $this->assertEquals(SubscriptionStatusProvider::FAILED, $this->statusProvider->getStatus()); + } + + /** + * @return array + */ + public function getStatusShouldBeFailedDataProvider() + { + return [ + 'Subscription update doesn\'t active' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, null], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, null] + ], + ], + 'Subscription update is active' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, 'http://store.com'], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, null] + ], + ], + ]; + } + + /** + * @param array $flagManagerData + * @param bool $isTokenExist + * @dataProvider getStatusShouldBePendingDataProvider + */ + public function testGetStatusShouldBePending(array $flagManagerData, bool $isTokenExist) + { + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn($isTokenExist); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(true); + + $this->expectFlagManagerReturn($flagManagerData); + $this->assertEquals(SubscriptionStatusProvider::PENDING, $this->statusProvider->getStatus()); + } + + /** + * @return array + */ + public function getStatusShouldBePendingDataProvider() + { + return [ + 'Subscription update doesn\'t active and the token does not exist' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, null], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, 45] + ], + 'isTokenExist' => false, + ], + 'Subscription update is active and the token does not exist' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, 'http://store.com'], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, 45] + ], + 'isTokenExist' => false, + ], + 'Subscription update is active and token exist' => [ + 'Flag Manager data mapping' => [ + [SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, 'http://store.com'], + [SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, null] + ], + 'isTokenExist' => true, + ], + ]; + } + + public function testGetStatusShouldBeEnabled() + { + $this->flagManagerMock + ->method('getFlagData') + ->with(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE) + ->willReturn(null); + $this->analyticsTokenMock->expects($this->once()) + ->method('isTokenExist') + ->willReturn(true); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(true); + $this->assertEquals(SubscriptionStatusProvider::ENABLED, $this->statusProvider->getStatus()); + } + + public function testGetStatusShouldBeDisabled() + { + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with('analytics/subscription/enabled') + ->willReturn(false); + $this->assertEquals(SubscriptionStatusProvider::DISABLED, $this->statusProvider->getStatus()); + } + + /** + * @param array $mapping + */ + private function expectFlagManagerReturn(array $mapping) + { + $this->flagManagerMock + ->method('getFlagData') + ->willReturnMap($mapping); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/Model/System/Message/NotificationAboutFailedSubscriptionTest.php b/app/code/Magento/Analytics/Test/Unit/Model/System/Message/NotificationAboutFailedSubscriptionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ad1d87488d751cf0713688688fafad033bb3df41 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/Model/System/Message/NotificationAboutFailedSubscriptionTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\Model\System\Message; + +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Analytics\Model\System\Message\NotificationAboutFailedSubscription; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\UrlInterface; + +/** + * Class NotificationAboutFailedSubscriptionTest + */ +class NotificationAboutFailedSubscriptionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|SubscriptionStatusProvider + */ + private $subscriptionStatusMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|UrlInterface + */ + private $urlBuilderMock; + + /** + * @var NotificationAboutFailedSubscription + */ + private $notification; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->subscriptionStatusMock = $this->getMockBuilder(SubscriptionStatusProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlBuilderMock = $this->getMockBuilder(UrlInterface::class) + ->getMockForAbstractClass(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->notification = $this->objectManagerHelper->getObject( + NotificationAboutFailedSubscription::class, + [ + 'subscriptionStatusProvider' => $this->subscriptionStatusMock, + 'urlBuilder' => $this->urlBuilderMock + ] + ); + } + + public function testIsDisplayedWhenMessageShouldBeDisplayed() + { + $this->subscriptionStatusMock->expects($this->once()) + ->method('getStatus') + ->willReturn( + SubscriptionStatusProvider::FAILED + ); + $this->assertTrue($this->notification->isDisplayed()); + } + + /** + * @dataProvider notDisplayedNotificationStatuses + * + * @param $status + */ + public function testIsDisplayedWhenMessageShouldNotBeDisplayed($status) + { + $this->subscriptionStatusMock->expects($this->once()) + ->method('getStatus') + ->willReturn($status); + $this->assertFalse($this->notification->isDisplayed()); + } + + public function testGetTextShouldBuildMessage() + { + $retryUrl = 'http://magento.dev/retryUrl'; + $this->urlBuilderMock->expects($this->once()) + ->method('getUrl') + ->with('analytics/subscription/retry') + ->willReturn($retryUrl); + $messageDetails = 'Failed to synchronize data to the Magento Business Intelligence service. '; + $messageDetails .= sprintf('<a href="%s">Retry Synchronization</a>', $retryUrl); + $this->assertEquals($messageDetails, $this->notification->getText()); + } + + /** + * Provide statuses according to which message should not be displayed. + * + * @return array + */ + public function notDisplayedNotificationStatuses() + { + return [ + [SubscriptionStatusProvider::PENDING], + [SubscriptionStatusProvider::DISABLED], + [SubscriptionStatusProvider::ENABLED], + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3f1ed9a5cf4c00863988b9020a2a92f9fb00a14a --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/Converter/XmlTest.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\Config\Converter; + +/** + * A unit test for testing of the reports configuration converter (XML to PHP array). + */ +class XmlTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\ReportXml\Config\Converter\Xml + */ + private $subject; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\Config\Converter\Xml::class + ); + } + + /** + * @return void + */ + public function testConvertNoElements() + { + $this->assertEmpty( + $this->subject->convert(new \DOMDocument()) + ); + } + + /** + * @return void + */ + public function testConvert() + { + $dom = new \DOMDocument(); + + $expectedArray = [ + 'config' => [ + [ + 'noNamespaceSchemaLocation' => 'urn:magento:module:Magento_Analytics:etc/reports.xsd', + 'report' => [ + [ + 'name' => 'test_report_1', + 'connection' => 'sales', + 'source' => [ + [ + 'name' => 'sales_order', + 'alias' => 'orders', + 'attribute' => [ + [ + 'name' => 'entity_id', + 'alias' => 'identifier', + ] + ], + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'entity_id', + 'operator' => 'gt', + '_value' => '10' + ] + ] + ] + ] + ] + ] + ], + [ + 'name' => 'test_report_2', + 'connection' => 'default', + 'source' => [ + [ + 'name' => 'customer_entity', + 'alias' => 'customers', + 'attribute' => [ + [ + 'name' => 'email' + ] + ], + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'dob', + 'operator' => 'null' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; + + $dom->loadXML(file_get_contents(__DIR__ . '/../_files/valid_reports.xml')); + + $this->assertEquals($expectedArray, $this->subject->convert($dom)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php new file mode 100644 index 0000000000000000000000000000000000000000..85343b6b301d617278c886b2dbf61633d7e46a19 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/MapperTest.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\Config; + +use Magento\Analytics\ReportXml\Config\Mapper; + +/** + * Class MapperTest + */ +class MapperTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Mapper + */ + private $mapper; + + protected function setUp() + { + $this->mapper = new Mapper(); + } + + public function testExecute() + { + $configData['config'][0]['report'] = [ + [ + 'source' => ['product'], + 'name' => 'Product', + ] + ]; + $expectedResult = [ + 'Product' => [ + 'source' => 'product', + 'name' => 'Product', + ] + ]; + $this->assertEquals($this->mapper->execute($configData), $expectedResult); + } + + public function testExecuteWithoutReports() + { + $configData = []; + $this->assertEquals($this->mapper->execute($configData), []); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..e04ee9616379710d58d189184e2d6f6558ca3a23 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/Config/_files/valid_reports.xml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="test_report_1" connection="sales"> + <source name="sales_order" alias="orders"> + <attribute name="entity_id" alias="identifier" /> + <filter glue="and"> + <condition attribute="entity_id" operator="gt">10</condition> + </filter> + </source> + </report> + <report name="test_report_2" connection="default"> + <source name="customer_entity" alias="customers"> + <attribute name="email" /> + <filter glue="and"> + <condition attribute="dob" operator="null" /> + </filter> + </source> + </report> +</config> diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cbc9aa129d8740a96b43a35aadb094d1a40a3349 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConfigTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +use Magento\Analytics\ReportXml\Config; +use Magento\Framework\Config\DataInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class ConfigTest + */ +class ConfigTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var DataInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Config + */ + private $config; + + /** + * @return void + */ + protected function setUp() + { + $this->dataMock = $this->getMockBuilder(DataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->config = $this->objectManagerHelper->getObject( + Config::class, + [ + 'data' => $this->dataMock, + ] + ); + } + + public function testGet() + { + $queryName = 'query string'; + $queryResult = [ 'query' => 1 ]; + + $this->dataMock + ->expects($this->once()) + ->method('get') + ->with($queryName) + ->willReturn($queryResult); + + $this->assertSame($queryResult, $this->config->get($queryName)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1e4ae9142c13dfe57fb6e3fb5b34e7299fe6a238 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ConnectionFactoryTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +use Magento\Analytics\ReportXml\ConnectionFactory; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\Pdo\Mysql as MysqlPdoAdapter; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class ConnectionFactoryTest + */ +class ConnectionFactoryTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var ConnectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionNewMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ConnectionFactory + */ + private $connectionFactory; + + /** + * @return void + */ + protected function setUp() + { + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(MysqlPdoAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionNewMock = $this->getMockBuilder(MysqlPdoAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->connectionFactory = $this->objectManagerHelper->getObject( + ConnectionFactory::class, + [ + 'resourceConnection' => $this->resourceConnectionMock, + 'objectManager' => $this->objectManagerMock, + ] + ); + } + + public function testGetConnection() + { + $connectionName = 'read'; + + $this->resourceConnectionMock + ->expects($this->once()) + ->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + + $this->connectionMock + ->expects($this->once()) + ->method('getConfig') + ->with() + ->willReturn(['persistent' => 1]); + + $this->objectManagerMock + ->expects($this->once()) + ->method('create') + ->with(get_class($this->connectionMock), ['config' => ['use_buffered_query' => false]]) + ->willReturn($this->connectionNewMock); + + $this->assertSame($this->connectionNewMock, $this->connectionFactory->getConnection($connectionName)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3b01105a8873b7688ef55f4513d67e2b2cc31517 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FilterAssemblerTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB\Assembler; + +/** + * A unit test for testing of the 'filter' assembler. + */ +class FilterAssemblerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\ReportXml\DB\Assembler\FilterAssembler + */ + private $subject; + + /** + * @var \Magento\Analytics\ReportXml\DB\NameResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $nameResolverMock; + + /** + * @var \Magento\Analytics\ReportXml\DB\SelectBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectBuilderMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @var \Magento\Analytics\ReportXml\DB\ConditionResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $conditionResolverMock; + + /** + * @return void + */ + protected function setUp() + { + $this->nameResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\NameResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->selectBuilderMock->expects($this->any()) + ->method('getFilters') + ->willReturn([]); + + $this->conditionResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ConditionResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\DB\Assembler\FilterAssembler::class, + [ + 'conditionResolver' => $this->conditionResolverMock, + 'nameResolver' => $this->nameResolverMock + ] + ); + } + + /** + * @return void + */ + public function testAssembleEmpty() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales' + ] + ]; + + $this->selectBuilderMock->expects($this->never()) + ->method('setFilters'); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } + + /** + * @return void + */ + public function testAssembleNotEmpty() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'entity_id', + 'operator' => 'null' + ] + ] + ] + ] + ] + ]; + + $this->nameResolverMock->expects($this->any()) + ->method('getAlias') + ->with($queryConfigMock['source']) + ->willReturn($queryConfigMock['source']['alias']); + + $this->conditionResolverMock->expects($this->once()) + ->method('getFilter') + ->with( + $this->selectBuilderMock, + $queryConfigMock['source']['filter'], + $queryConfigMock['source']['alias'] + ) + ->willReturn('(sales.entity_id IS NULL)'); + + $this->selectBuilderMock->expects($this->once()) + ->method('setFilters') + ->with(['(sales.entity_id IS NULL)']); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..575db94a7b7e199e3e0261549fd65500af5c9f68 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/FromAssemblerTest.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB\Assembler; + +use Magento\Framework\App\ResourceConnection; + +/** + * A unit test for testing of the 'from' assembler. + */ +class FromAssemblerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\ReportXml\DB\Assembler\FromAssembler + */ + private $subject; + + /** + * @var \Magento\Analytics\ReportXml\DB\NameResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $nameResolverMock; + + /** + * @var \Magento\Analytics\ReportXml\DB\SelectBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectBuilderMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @var \Magento\Analytics\ReportXml\DB\ColumnsResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $columnsResolverMock; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnection; + + /** + * @return void + */ + protected function setUp() + { + $this->nameResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\NameResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->selectBuilderMock->expects($this->any()) + ->method('getColumns') + ->willReturn([]); + + $this->columnsResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ColumnsResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\DB\Assembler\FromAssembler::class, + [ + 'nameResolver' => $this->nameResolverMock, + 'columnsResolver' => $this->columnsResolverMock, + 'resourceConnection' => $this->resourceConnection, + ] + ); + } + + /** + * @dataProvider assembleDataProvider + * @param array $queryConfig + * @param string $tableName + * @return void + */ + public function testAssemble(array $queryConfig, $tableName) + { + $this->nameResolverMock->expects($this->any()) + ->method('getAlias') + ->with($queryConfig['source']) + ->willReturn($queryConfig['source']['alias']); + + $this->nameResolverMock->expects($this->once()) + ->method('getName') + ->with($queryConfig['source']) + ->willReturn($queryConfig['source']['name']); + + $this->resourceConnection + ->expects($this->once()) + ->method('getTableName') + ->with($queryConfig['source']['name']) + ->willReturn($tableName); + + $this->selectBuilderMock->expects($this->once()) + ->method('setFrom') + ->with([$queryConfig['source']['alias'] => $tableName]); + + $this->columnsResolverMock->expects($this->once()) + ->method('getColumns') + ->with($this->selectBuilderMock, $queryConfig['source']) + ->willReturn(['entity_id' => 'sales.entity_id']); + + $this->selectBuilderMock->expects($this->once()) + ->method('setColumns') + ->with(['entity_id' => 'sales.entity_id']); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfig) + ); + } + + /** + * @return array + */ + public function assembleDataProvider() + { + return [ + 'Tables without prefixes' => [ + [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'attribute' => [ + [ + 'name' => 'entity_id' + ] + ], + ], + ], + 'sales_order', + ], + 'Tables with prefixes' => [ + [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'attribute' => [ + [ + 'name' => 'entity_id' + ] + ], + ], + ], + 'pref_sales_order', + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..aaafd731552a092eb159e6ddb89d7f9acb0f19e6 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/Assembler/JoinAssemblerTest.php @@ -0,0 +1,279 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB\Assembler; + +use Magento\Framework\App\ResourceConnection; + +/** + * A unit test for testing of the 'join' assembler. + */ +class JoinAssemblerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\ReportXml\DB\Assembler\JoinAssembler + */ + private $subject; + + /** + * @var \Magento\Analytics\ReportXml\DB\NameResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $nameResolverMock; + + /** + * @var \Magento\Analytics\ReportXml\DB\SelectBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectBuilderMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @var \Magento\Analytics\ReportXml\DB\ColumnsResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $columnsResolverMock; + + /** + * @var \Magento\Analytics\ReportXml\DB\ConditionResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $conditionResolverMock; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnection; + + /** + * @return void + */ + protected function setUp() + { + $this->nameResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\NameResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->selectBuilderMock->expects($this->any()) + ->method('getFilters') + ->willReturn([]); + $this->selectBuilderMock->expects($this->any()) + ->method('getColumns') + ->willReturn([]); + $this->selectBuilderMock->expects($this->any()) + ->method('getJoins') + ->willReturn([]); + + $this->columnsResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ColumnsResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->conditionResolverMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\ConditionResolver::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnection = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\DB\Assembler\JoinAssembler::class, + [ + 'conditionResolver' => $this->conditionResolverMock, + 'nameResolver' => $this->nameResolverMock, + 'columnsResolver' => $this->columnsResolverMock, + 'resourceConnection' => $this->resourceConnection, + ] + ); + } + + /** + * @return void + */ + public function testAssembleEmpty() + { + $queryConfigMock = [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales' + ] + ]; + + $this->selectBuilderMock->expects($this->never()) + ->method('setColumns'); + $this->selectBuilderMock->expects($this->never()) + ->method('setFilters'); + $this->selectBuilderMock->expects($this->never()) + ->method('setJoins'); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } + + /** + * @param array $queryConfigMock + * @param array $joinsMock + * @param array $tablesMapping + * @return void + * @dataProvider assembleNotEmptyDataProvider + */ + public function testAssembleNotEmpty(array $queryConfigMock, array $joinsMock, array $tablesMapping) + { + $filtersMock = []; + + $this->nameResolverMock->expects($this->at(0)) + ->method('getAlias') + ->with($queryConfigMock['source']) + ->willReturn($queryConfigMock['source']['alias']); + $this->nameResolverMock->expects($this->at(1)) + ->method('getAlias') + ->with($queryConfigMock['source']['link-source'][0]) + ->willReturn($queryConfigMock['source']['link-source'][0]['alias']); + $this->nameResolverMock->expects($this->once()) + ->method('getName') + ->with($queryConfigMock['source']['link-source'][0]) + ->willReturn($queryConfigMock['source']['link-source'][0]['name']); + + $this->resourceConnection + ->expects($this->any()) + ->method('getTableName') + ->willReturnOnConsecutiveCalls(...array_values($tablesMapping)); + + $this->conditionResolverMock->expects($this->at(0)) + ->method('getFilter') + ->with( + $this->selectBuilderMock, + $queryConfigMock['source']['link-source'][0]['using'], + $queryConfigMock['source']['link-source'][0]['alias'], + $queryConfigMock['source']['alias'] + ) + ->willReturn('(billing.parent_id = `sales`.`entity_id`)'); + + if (isset($queryConfigMock['source']['link-source'][0]['filter'])) { + $filtersMock = ['(sales.entity_id IS NULL)']; + + $this->conditionResolverMock->expects($this->at(1)) + ->method('getFilter') + ->with( + $this->selectBuilderMock, + $queryConfigMock['source']['link-source'][0]['filter'], + $queryConfigMock['source']['link-source'][0]['alias'], + $queryConfigMock['source']['alias'] + ) + ->willReturn($filtersMock[0]); + + $this->columnsResolverMock->expects($this->once()) + ->method('getColumns') + ->with($this->selectBuilderMock, $queryConfigMock['source']['link-source'][0]) + ->willReturn( + [ + 'entity_id' => 'sales.entity_id', + 'billing_address_id' => 'billing.entity_id' + ] + ); + + $this->selectBuilderMock->expects($this->once()) + ->method('setColumns') + ->with( + [ + 'entity_id' => 'sales.entity_id', + 'billing_address_id' => 'billing.entity_id' + ] + ); + } + + $this->selectBuilderMock->expects($this->once()) + ->method('setFilters') + ->with($filtersMock); + $this->selectBuilderMock->expects($this->once()) + ->method('setJoins') + ->with($joinsMock); + + $this->assertEquals( + $this->selectBuilderMock, + $this->subject->assemble($this->selectBuilderMock, $queryConfigMock) + ); + } + + /** + * @return array + */ + public function assembleNotEmptyDataProvider() + { + return [ + [ + [ + 'source' => [ + 'name' => 'sales_order', + 'alias' => 'sales', + 'link-source' => [ + [ + 'name' => 'sales_order_address', + 'alias' => 'billing', + 'link-type' => 'left', + 'attribute' => [ + [ + 'alias' => 'billing_address_id', + 'name' => 'entity_id' + ] + ], + 'using' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'parent_id', + 'operator' => 'eq', + 'type' => 'identifier', + '_value' => 'entity_id' + ] + ] + ] + ], + 'filter' => [ + [ + 'glue' => 'and', + 'condition' => [ + [ + 'attribute' => 'entity_id', + 'operator' => 'null' + ] + ] + ] + ] + ] + ] + ] + ], + [ + 'billing' => [ + 'link-type' => 'left', + 'table' => [ + 'billing' => 'pref_sales_order_address' + ], + 'condition' => '(billing.parent_id = `sales`.`entity_id`)' + ] + ], + ['sales_order_address' => 'pref_sales_order_address'] + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bdbe3d1d22c2239091499e85b461134a9c3d1453 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ColumnsResolverTest.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB; + +use Magento\Analytics\ReportXml\DB\ColumnsResolver; +use Magento\Analytics\ReportXml\DB\NameResolver; +use Magento\Analytics\ReportXml\DB\SelectBuilder; +use Magento\Framework\DB\Sql\ColumnValueExpression; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class ColumnsResolverTest + */ +class ColumnsResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SelectBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectBuilderMock; + + /** + * @var ColumnsResolver + */ + private $columnsResolver; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @return void + */ + protected function setUp() + { + $this->selectBuilderMock = $this->getMockBuilder(SelectBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new ObjectManagerHelper($this); + $this->columnsResolver = $objectManager->getObject( + ColumnsResolver::class, + [ + 'nameResolver' => new NameResolver(), + 'resourceConnection' => $this->resourceConnectionMock + ] + ); + } + + public function testGetColumnsWithoutAttributes() + { + $this->assertEquals($this->columnsResolver->getColumns($this->selectBuilderMock, []), []); + } + + /** + * @dataProvider getColumnsDataProvider + */ + public function testGetColumnsWithFunction($expectedColumns, $expectedGroup, $entityConfig) + { + $this->resourceConnectionMock->expects($this->any()) + ->method('getConnection') + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->any()) + ->method('quoteIdentifier') + ->with('cpe.name') + ->willReturn('`cpe`.`name`'); + $this->selectBuilderMock->expects($this->once()) + ->method('getColumns') + ->willReturn([]); + $this->selectBuilderMock->expects($this->once()) + ->method('getGroup') + ->willReturn([]); + $this->selectBuilderMock->expects($this->once()) + ->method('setGroup') + ->with($expectedGroup); + $this->assertEquals( + $expectedColumns, + $this->columnsResolver->getColumns( + $this->selectBuilderMock, + $entityConfig + ) + ); + } + + /** + * @return array + */ + public function getColumnsDataProvider() + { + return [ + 'COUNT( DISTINCT `cpe`.`name`) AS name' => [ + 'expectedColumns' => [ + 'name' => new ColumnValueExpression('COUNT( DISTINCT `cpe`.`name`)') + ], + 'expectedGroup' => [ + 'name' => new ColumnValueExpression('COUNT( DISTINCT `cpe`.`name`)') + ], + 'entityConfig' => + [ + 'name' => 'catalog_product_entity', + 'alias' => 'cpe', + 'attribute' => [ + [ + 'name' => 'name', + 'function' => 'COUNT', + 'distinct' => true, + 'group' => true + ] + ], + ], + ], + 'AVG(`cpe`.`name`) AS avg_name' => [ + 'expectedColumns' => [ + 'avg_name' => new ColumnValueExpression('AVG(`cpe`.`name`)') + ], + 'expectedGroup' => [], + 'entityConfig' => + [ + 'name' => 'catalog_product_entity', + 'alias' => 'cpe', + 'attribute' => [ + [ + 'name' => 'name', + 'alias' => 'avg_name', + 'function' => 'AVG', + ] + ], + ], + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c8182d068fba546e4c99fb913e04b5ebba1fc8c1 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ConditionResolverTest.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB; + +use Magento\Analytics\ReportXml\DB\ConditionResolver; +use Magento\Analytics\ReportXml\DB\SelectBuilder; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Sql\Expression; + +/** + * Class ConditionResolverTest + */ +class ConditionResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var ConditionResolver + */ + private $conditionResolver; + + /** + * @var SelectBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectBuilderMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @return void + */ + protected function setUp() + { + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderMock = $this->getMockBuilder(SelectBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->conditionResolver = new ConditionResolver($this->resourceConnectionMock); + } + + public function testGetFilter() + { + $condition = ["type" => "variable", "_value" => "1", "attribute" => "id", "operator" => "neq"]; + $valueCondition = ["type" => "value", "_value" => "2", "attribute" => "first_name", "operator" => "eq"]; + $identifierCondition = [ + "type" => "identifier", + "_value" => "other_field", + "attribute" => "last_name", + "operator" => "eq"]; + $filter = [["glue" => "AND", "condition" => [$valueCondition]]]; + $filterConfig = [ + ["glue" => "OR", "condition" => [$condition], 'filter' => $filter], + ["glue" => "OR", "condition" => [$identifierCondition]], + ]; + $aliasName = 'n'; + $this->selectBuilderMock->expects($this->any()) + ->method('setParams') + ->with(array_merge([], [$condition['_value']])); + + $this->selectBuilderMock->expects($this->once()) + ->method('getParams') + ->willReturn([]); + + $this->selectBuilderMock->expects($this->any()) + ->method('getColumns') + ->willReturn(['price' => new Expression("(n.price = 400)")]); + + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); + + $this->connectionMock->expects($this->any()) + ->method('quote') + ->willReturn("'John'"); + $this->connectionMock->expects($this->exactly(4)) + ->method('quoteIdentifier') + ->willReturnMap([ + ['n.id', false, '`n`.`id`'], + ['n.first_name', false, '`n`.`first_name`'], + ['n.last_name', false, '`n`.`last_name`'], + ['other_field', false, '`other_field`'], + ]); + + $result = "(`n`.`id` != 1 OR ((`n`.`first_name` = 'John'))) OR (`n`.`last_name` = `other_field`)"; + $this->assertEquals( + $result, + $this->conditionResolver->getFilter($this->selectBuilderMock, $filterConfig, $aliasName) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4accd03aef3ea9fd770b3bb21265b41c3db04ceb --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/NameResolverTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB; + +use Magento\Analytics\ReportXml\DB\NameResolver; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class NameResolverTest + */ +class NameResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var NameResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $nameResolverMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var NameResolver + */ + private $nameResolver; + + /** + * @return void + */ + protected function setUp() + { + $this->nameResolverMock = $this->getMockBuilder(NameResolver::class) + ->disableOriginalConstructor() + ->setMethods(['getName']) + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->nameResolver = $this->objectManagerHelper->getObject(NameResolver::class); + } + + public function testGetName() + { + $elementConfigMock = [ + 'name' => 'sales_order', + 'alias' => 'sales', + ]; + + $this->assertSame('sales_order', $this->nameResolver->getName($elementConfigMock)); + } + + /** + * @param array $elementConfig + * @param string|null $elementAlias + * + * @dataProvider getAliasDataProvider + */ + public function testGetAlias($elementConfig, $elementAlias) + { + $elementName = 'elementName'; + + $this->nameResolverMock + ->expects($this->once()) + ->method('getName') + ->with($elementConfig) + ->willReturn($elementName); + + $this->assertSame($elementAlias ?: $elementName, $this->nameResolverMock->getAlias($elementConfig)); + } + + /** + * @return array + */ + public function getAliasDataProvider() + { + return [ + 'ElementConfigWithAliases' => [ + ['alias' => 'sales', 'name' => 'sales_order'], + 'sales', + ], + 'ElementConfigWithoutAliases' => [ + ['name' => 'sales_order'], + null, + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bbb9ca4b511b619a024de3a8e983877422505dba --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/ReportValidatorTest.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB; + +use Magento\Analytics\ReportXml\ConnectionFactory; +use Magento\Analytics\ReportXml\DB\ReportValidator; +use Magento\Analytics\ReportXml\Query; +use Magento\Analytics\ReportXml\QueryFactory; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class ReportValidatorTest + */ +class ReportValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ConnectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionFactoryMock; + + /** + * @var QueryFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryFactoryMock; + + /** + * @var Query|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ReportValidator + */ + private $reportValidator; + + /** + * @return void + */ + protected function setUp() + { + $this->connectionFactoryMock = $this->getMockBuilder(ConnectionFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->queryFactoryMock = $this->getMockBuilder(QueryFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->queryMock = $this->getMockBuilder(Query::class)->disableOriginalConstructor() + ->getMock(); + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass(); + $this->selectMock = $this->getMockBuilder(Select::class)->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->reportValidator = $this->objectManagerHelper->getObject( + ReportValidator::class, + [ + 'connectionFactory' => $this->connectionFactoryMock, + 'queryFactory' => $this->queryFactoryMock + ] + ); + } + + /** + * @dataProvider errorDataProvider + * @param string $reportName + * @param array $result + * @param \PHPUnit_Framework_MockObject_Stub $queryReturnStub + */ + public function testValidate($reportName, $result, \PHPUnit_Framework_MockObject_Stub $queryReturnStub) + { + $connectionName = 'testConnection'; + $this->queryFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->queryMock); + $this->queryMock->expects($this->once())->method('getConnectionName')->willReturn($connectionName); + $this->connectionFactoryMock->expects($this->once())->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + $this->queryMock->expects($this->atLeastOnce())->method('getSelect')->willReturn($this->selectMock); + $this->selectMock->expects($this->once())->method('limit')->with(0); + $this->connectionMock->expects($this->once())->method('query')->with($this->selectMock)->will($queryReturnStub); + $this->assertEquals($result, $this->reportValidator->validate($reportName)); + } + + /** + * Provide variations of the error returning + * + * @return array + */ + public function errorDataProvider() + { + $reportName = 'test'; + $errorMessage = 'SQL Error 42'; + return [ + [ + $reportName, + 'expectedResult' => [], + 'queryReturnStub' => $this->returnValue(null) + ], + [ + $reportName, + 'expectedResult' => [$reportName, $errorMessage], + 'queryReturnStub' => $this->throwException(new \Zend_Db_Statement_Exception($errorMessage)) + ] + ]; + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a82a187cdb3f8e4cd0422c75ee5a28b512fb3ade --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/DB/SelectBuilderTest.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml\DB; + +use Magento\Analytics\ReportXml\DB\SelectBuilder; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; + +/** + * Class SelectBuilderTest + */ +class SelectBuilderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SelectBuilder + */ + private $selectBuilder; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * @return void + */ + protected function setUp() + { + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilder = new SelectBuilder($this->resourceConnectionMock); + } + + public function testCreate() + { + $connectionName = 'MySql'; + $from = ['customer c']; + $columns = ['id', 'name', 'price']; + $filter = 'filter'; + $joins = [ + ['link-type' => 'left', 'table' => 'customer', 'condition' => 'in'], + ['link-type' => 'inner', 'table' => 'price', 'condition' => 'eq'], + ['link-type' => 'right', 'table' => 'attribute', 'condition' => 'neq'], + ]; + $groups = ['id', 'name']; + $this->selectBuilder->setConnectionName($connectionName); + $this->selectBuilder->setFrom($from); + $this->selectBuilder->setColumns($columns); + $this->selectBuilder->setFilters([$filter]); + $this->selectBuilder->setJoins($joins); + $this->selectBuilder->setGroup($groups); + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($this->selectMock); + $this->selectMock->expects($this->once()) + ->method('from') + ->with($from, []); + $this->selectMock->expects($this->once()) + ->method('columns') + ->with($columns); + $this->selectMock->expects($this->once()) + ->method('where') + ->with($filter); + $this->selectMock->expects($this->once()) + ->method('joinLeft') + ->with($joins[0]['table'], $joins[0]['condition'], []); + $this->selectMock->expects($this->once()) + ->method('joinInner') + ->with($joins[1]['table'], $joins[1]['condition'], []); + $this->selectMock->expects($this->once()) + ->method('joinRight') + ->with($joins[2]['table'], $joins[2]['condition'], []); + $this->selectBuilder->create(); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1d3f293ed676a6bfc37d2dff1b4c461deb67df64 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/IteratorFactoryTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +use Magento\Analytics\ReportXml\IteratorFactory; +use Magento\Framework\ObjectManagerInterface; + +/** + * Class IteratorFactoryTest + */ +class IteratorFactoryTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var \IteratorIterator|\PHPUnit_Framework_MockObject_MockObject + */ + private $iteratorIteratorMock; + + /** + * @var IteratorFactory + */ + private $iteratorFactory; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->iteratorIteratorMock = $this->getMockBuilder(\IteratorIterator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->iteratorFactory = new IteratorFactory( + $this->objectManagerMock + ); + } + + public function testCreate() + { + $arrayObject = new \ArrayIterator([1, 2, 3, 4, 5]); + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with(\IteratorIterator::class, ['iterator' => $arrayObject]) + ->willReturn($this->iteratorIteratorMock); + + $this->assertEquals($this->iteratorFactory->create($arrayObject), $this->iteratorIteratorMock); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9a3805a50f16765b6e306ef22369a1952e06779e --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryFactoryTest.php @@ -0,0 +1,239 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +/** + * A unit test for testing of the query factory. + */ +class QueryFactoryTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\ReportXml\QueryFactory + */ + private $subject; + + /** + * @var \Magento\Analytics\ReportXml\Query|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryMock; + + /** + * @var \Magento\Analytics\ReportXml\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * @var \Magento\Analytics\ReportXml\DB\Assembler\AssemblerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $assemblerMock; + + /** + * @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryCacheMock; + + /** + * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var \Magento\Analytics\ReportXml\SelectHydrator|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectHydratorMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @var \Magento\Analytics\ReportXml\DB\SelectBuilderFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectBuilderFactoryMock; + + /** + * @return void + */ + protected function setUp() + { + $this->queryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\Query::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->configMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\Config::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder( + \Magento\Framework\DB\Select::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->assemblerMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\Assembler\AssemblerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryCacheMock = $this->getMockBuilder( + \Magento\Framework\App\CacheInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerMock = $this->getMockBuilder( + \Magento\Framework\ObjectManagerInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectHydratorMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\SelectHydrator::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectBuilderFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilderFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\QueryFactory::class, + [ + 'config' => $this->configMock, + 'selectBuilderFactory' => $this->selectBuilderFactoryMock, + 'assemblers' => [$this->assemblerMock], + 'queryCache' => $this->queryCacheMock, + 'objectManager' => $this->objectManagerMock, + 'selectHydrator' => $this->selectHydratorMock + ] + ); + } + + /** + * @return void + */ + public function testCreateCached() + { + $queryName = 'test_query'; + + $this->queryCacheMock->expects($this->any()) + ->method('load') + ->with($queryName) + ->willReturn('{"connectionName":"sales","config":{},"select_parts":{}}'); + + $this->selectHydratorMock->expects($this->any()) + ->method('recreate') + ->with([]) + ->willReturn($this->selectMock); + + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with( + \Magento\Analytics\ReportXml\Query::class, + [ + 'select' => $this->selectMock, + 'selectHydrator' => $this->selectHydratorMock, + 'connectionName' => 'sales', + 'config' => [] + ] + ) + ->willReturn($this->queryMock); + + $this->queryCacheMock->expects($this->never()) + ->method('save'); + + $this->assertEquals( + $this->queryMock, + $this->subject->create($queryName) + ); + } + + /** + * @return void + */ + public function testCreateNotCached() + { + $queryName = 'test_query'; + + $queryConfigMock = [ + 'name' => 'test_query', + 'connection' => 'sales' + ]; + + $selectBuilderMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\DB\SelectBuilder::class + ) + ->disableOriginalConstructor() + ->getMock(); + $selectBuilderMock->expects($this->once()) + ->method('setConnectionName') + ->with($queryConfigMock['connection']); + $selectBuilderMock->expects($this->any()) + ->method('create') + ->willReturn($this->selectMock); + $selectBuilderMock->expects($this->any()) + ->method('getConnectionName') + ->willReturn($queryConfigMock['connection']); + + $this->queryCacheMock->expects($this->any()) + ->method('load') + ->with($queryName) + ->willReturn(null); + + $this->configMock->expects($this->any()) + ->method('get') + ->with($queryName) + ->willReturn($queryConfigMock); + + $this->selectBuilderFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($selectBuilderMock); + + $this->assemblerMock->expects($this->once()) + ->method('assemble') + ->with($selectBuilderMock, $queryConfigMock) + ->willReturn($selectBuilderMock); + + $this->objectManagerMock->expects($this->once()) + ->method('create') + ->with( + \Magento\Analytics\ReportXml\Query::class, + [ + 'select' => $this->selectMock, + 'selectHydrator' => $this->selectHydratorMock, + 'connectionName' => $queryConfigMock['connection'], + 'config' => $queryConfigMock + ] + ) + ->willReturn($this->queryMock); + + $this->queryCacheMock->expects($this->once()) + ->method('save') + ->with(json_encode($this->queryMock), $queryName); + + $this->assertEquals( + $this->queryMock, + $this->subject->create($queryName) + ); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a4b08a9ce5e0a51095de6aa5274f17815ba31bc9 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/QueryTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +use Magento\Analytics\ReportXml\Query; +use Magento\Analytics\ReportXml\SelectHydrator as selectHydrator; +use Magento\Framework\DB\Select; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class QueryTest + */ +class QueryTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * @var selectHydrator|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectHydratorMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var Query + */ + private $query; + + /** + * @var string + */ + private $connectionName = 'test_connection'; + + /** + * @return void + */ + protected function setUp() + { + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectHydratorMock = $this->getMockBuilder(selectHydrator::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->query = $this->objectManagerHelper->getObject( + Query::class, + [ + 'select' => $this->selectMock, + 'connectionName' => $this->connectionName, + 'selectHydrator' => $this->selectHydratorMock, + 'config' => [] + ] + ); + } + + /** + * @return void + */ + public function testJsonSerialize() + { + $selectParts = ['part' => 1]; + + $this->selectHydratorMock + ->expects($this->once()) + ->method('extract') + ->with($this->selectMock) + ->willReturn($selectParts); + + $expectedResult = [ + 'connectionName' => $this->connectionName, + 'select_parts' => $selectParts, + 'config' => [] + ]; + + $this->assertSame($expectedResult, $this->query->jsonSerialize()); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5f329993dd291a746a30f6bc32e8bdde2d55ecbc --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/ReportProviderTest.php @@ -0,0 +1,180 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +/** + * A unit test for testing of the reports provider. + */ +class ReportProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Analytics\ReportXml\ReportProvider + */ + private $subject; + + /** + * @var \Magento\Analytics\ReportXml\Query|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryMock; + + /** + * @var \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * @var \IteratorIterator|\PHPUnit_Framework_MockObject_MockObject + */ + private $iteratorMock; + + /** + * @var \Magento\Framework\DB\Statement\Pdo\Mysql|\PHPUnit_Framework_MockObject_MockObject + */ + private $statementMock; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var \Magento\Analytics\ReportXml\QueryFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $queryFactoryMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @var \Magento\Analytics\ReportXml\ConnectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionFactoryMock; + + /** + * @var \Magento\Analytics\ReportXml\IteratorFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $iteratorFactoryMock; + + /** + * @return void + */ + protected function setUp() + { + $this->selectMock = $this->getMockBuilder( + \Magento\Framework\DB\Select::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\Query::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->queryMock->expects($this->any()) + ->method('getSelect') + ->willReturn($this->selectMock); + + $this->iteratorMock = $this->getMockBuilder( + \IteratorIterator::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->statementMock = $this->getMockBuilder( + \Magento\Framework\DB\Statement\Pdo\Mysql::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->statementMock->expects($this->any()) + ->method('getIterator') + ->willReturn($this->iteratorMock); + + $this->connectionMock = $this->getMockBuilder( + \Magento\Framework\DB\Adapter\AdapterInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->queryFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\QueryFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->iteratorFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\IteratorFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->iteratorMock = $this->getMockBuilder( + \IteratorIterator::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManagerHelper = + new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->connectionFactoryMock = $this->getMockBuilder( + \Magento\Analytics\ReportXml\ConnectionFactory::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->subject = $this->objectManagerHelper->getObject( + \Magento\Analytics\ReportXml\ReportProvider::class, + [ + 'queryFactory' => $this->queryFactoryMock, + 'connectionFactory' => $this->connectionFactoryMock, + 'iteratorFactory' => $this->iteratorFactoryMock + ] + ); + } + + /** + * @return void + */ + public function testGetReport() + { + $reportName = 'test_report'; + $connectionName = 'sales'; + + $this->queryFactoryMock->expects($this->once()) + ->method('create') + ->with($reportName) + ->willReturn($this->queryMock); + + $this->connectionFactoryMock->expects($this->once()) + ->method('getConnection') + ->with($connectionName) + ->willReturn($this->connectionMock); + + $this->queryMock->expects($this->once()) + ->method('getConnectionName') + ->willReturn($connectionName); + + $this->queryMock->expects($this->once()) + ->method('getConfig') + ->willReturn( + [ + 'connection' => $connectionName + ] + ); + + $this->connectionMock->expects($this->once()) + ->method('query') + ->with($this->selectMock) + ->willReturn($this->statementMock); + + $this->iteratorFactoryMock->expects($this->once()) + ->method('create') + ->with($this->statementMock, null) + ->willReturn($this->iteratorMock); + $this->assertEquals($this->iteratorMock, $this->subject->getReport($reportName)); + } +} diff --git a/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php b/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ce57a1eca3689a2139d4dca130016c5d951b9e17 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Unit/ReportXml/SelectHydratorTest.php @@ -0,0 +1,257 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Unit\ReportXml; + +use Magento\Analytics\ReportXml\SelectHydrator; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\DB\Sql\JsonSerializableExpression; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Class SelectHydratorTest + */ +class SelectHydratorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SelectHydrator + */ + private $selectHydrator; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionMock; + + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $selectMock; + + /** + * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @return void + */ + protected function setUp() + { + $this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->selectHydrator = $this->objectManagerHelper->getObject( + SelectHydrator::class, + [ + 'resourceConnection' => $this->resourceConnectionMock, + 'objectManager' => $this->objectManagerMock, + ] + ); + } + + public function testExtract() + { + $selectParts = + [ + Select::DISTINCT, + Select::COLUMNS, + Select::UNION, + Select::FROM, + Select::WHERE, + Select::GROUP, + Select::HAVING, + Select::ORDER, + Select::LIMIT_COUNT, + Select::LIMIT_OFFSET, + Select::FOR_UPDATE + ]; + + $result = []; + foreach ($selectParts as $part) { + $result[$part] = "Part"; + } + $this->selectMock->expects($this->any()) + ->method('getPart') + ->willReturn("Part"); + $this->assertEquals($this->selectHydrator->extract($this->selectMock), $result); + } + + /** + * @dataProvider recreateWithoutExpressionDataProvider + * @param array $selectParts + * @param array $parts + * @param array $partValues + */ + public function testRecreateWithoutExpression($selectParts, $parts, $partValues) + { + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); + $this->connectionMock->expects($this->once()) + ->method('select') + ->willReturn($this->selectMock); + foreach ($parts as $key => $part) { + $this->selectMock->expects($this->at($key)) + ->method('setPart') + ->with($part, $partValues[$key]); + } + + $this->assertSame($this->selectMock, $this->selectHydrator->recreate($selectParts)); + } + + /** + * @return array + */ + public function recreateWithoutExpressionDataProvider() + { + return [ + 'Select without expressions' => [ + [ + Select::COLUMNS => [ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + 'field_name_2', + 'alias_2', + ], + ] + ], + [Select::COLUMNS], + [[ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + 'field_name_2', + 'alias_2', + ], + ]], + ], + ]; + } + + /** + * @dataProvider recreateWithExpressionDataProvider + * @param array $selectParts + * @param array $expectedParts + * @param \PHPUnit_Framework_MockObject_MockObject[] $expressionMocks + */ + public function testRecreateWithExpression( + array $selectParts, + array $expectedParts, + array $expressionMocks + ) { + $this->objectManagerMock + ->expects($this->exactly(count($expressionMocks))) + ->method('create') + ->with($this->isType('string'), $this->isType('array')) + ->willReturnOnConsecutiveCalls(...$expressionMocks); + $this->resourceConnectionMock + ->expects($this->once()) + ->method('getConnection') + ->with() + ->willReturn($this->connectionMock); + $this->connectionMock + ->expects($this->once()) + ->method('select') + ->with() + ->willReturn($this->selectMock); + foreach (array_keys($selectParts) as $key => $partName) { + $this->selectMock + ->expects($this->at($key)) + ->method('setPart') + ->with($partName, $expectedParts[$partName]); + } + + $this->assertSame($this->selectMock, $this->selectHydrator->recreate($selectParts)); + } + + /** + * @return array + */ + public function recreateWithExpressionDataProvider() + { + $expressionMock = $this->getMockBuilder(JsonSerializableExpression::class) + ->disableOriginalConstructor() + ->getMock(); + + return [ + 'Select without expressions' => [ + 'Parts' => [ + Select::COLUMNS => [ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + [ + 'class' => 'Some_class', + 'arguments' => [ + 'expression' => ['some(expression)'] + ] + ], + 'alias_2', + ], + ] + ], + 'expectedParts' => [ + Select::COLUMNS => [ + [ + 'table_name', + 'field_name', + 'alias', + ], + [ + 'table_name', + $expressionMock, + 'alias_2', + ], + ] + ], + 'expectedExpressions' => [ + $expressionMock + ] + ], + ]; + } +} diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..edc3443e487b6067499a45ba930f0d7382c24b32 --- /dev/null +++ b/app/code/Magento/Analytics/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/module-backend": "100.2.*", + "magento/module-config": "100.2.*", + "magento/module-integration": "100.2.*", + "magento/module-store": "100.2.*", + "magento/framework": "100.2.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Analytics\\": "" + } + } +} diff --git a/app/code/Magento/Analytics/docs/images/M2_MA_signup.png b/app/code/Magento/Analytics/docs/images/M2_MA_signup.png new file mode 100644 index 0000000000000000000000000000000000000000..78ed8fad92881ddc1eae7f474f3d4dd88daf2cc7 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/M2_MA_signup.png differ diff --git a/app/code/Magento/Analytics/docs/images/analytics_modules.png b/app/code/Magento/Analytics/docs/images/analytics_modules.png new file mode 100644 index 0000000000000000000000000000000000000000..0bf6048b0d9ccac18eb144d5b1ab9077ea734dec Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/analytics_modules.png differ diff --git a/app/code/Magento/Analytics/docs/images/data_transition.png b/app/code/Magento/Analytics/docs/images/data_transition.png new file mode 100644 index 0000000000000000000000000000000000000000..a75e97983e15d1d7f1e3451a53e02fa6a13a1009 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/data_transition.png differ diff --git a/app/code/Magento/Analytics/docs/images/definition.png b/app/code/Magento/Analytics/docs/images/definition.png new file mode 100644 index 0000000000000000000000000000000000000000..16acc576320b0173e0078192f4c1b4bd3f1938fc Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/definition.png differ diff --git a/app/code/Magento/Analytics/docs/images/mbi_file_exchange.png b/app/code/Magento/Analytics/docs/images/mbi_file_exchange.png new file mode 100644 index 0000000000000000000000000000000000000000..f39d2e4900703f232b4729e524f9b37c63707cf1 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/mbi_file_exchange.png differ diff --git a/app/code/Magento/Analytics/docs/images/signup.png b/app/code/Magento/Analytics/docs/images/signup.png new file mode 100644 index 0000000000000000000000000000000000000000..561e18b3a351f451b4397f0e4035e2468fcb2585 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/signup.png differ diff --git a/app/code/Magento/Analytics/docs/images/update.png b/app/code/Magento/Analytics/docs/images/update.png new file mode 100644 index 0000000000000000000000000000000000000000..149f5b5d3f9bd4f2ff6c320f07fc4361b0e45557 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/update.png differ diff --git a/app/code/Magento/Analytics/docs/images/update_request.png b/app/code/Magento/Analytics/docs/images/update_request.png new file mode 100644 index 0000000000000000000000000000000000000000..7181251e3634efcc49ddfe412c3c8d3509c93665 Binary files /dev/null and b/app/code/Magento/Analytics/docs/images/update_request.png differ diff --git a/app/code/Magento/Analytics/etc/acl.xml b/app/code/Magento/Analytics/etc/acl.xml new file mode 100644 index 0000000000000000000000000000000000000000..bf2251895f929b3a86ec071318623bc4e36d640d --- /dev/null +++ b/app/code/Magento/Analytics/etc/acl.xml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd"> + <acl> + <resources> + <resource id="Magento_Backend::admin"> + <resource id="Magento_Analytics::analytics" title="Analytics" translate="title" sortOrder="10"> + <resource id="Magento_Analytics::analytics_api" title="API" translate="title" sortOrder="10"/> + </resource> + <resource id="Magento_Backend::stores"> + <resource id="Magento_Backend::stores_settings"> + <resource id="Magento_Config::config" title="Configuration" translate="title" sortOrder="20"> + <resource id="Magento_Analytics::analytics_settings" title="Analytics" translate="title" sortOrder="150" /> + </resource> + </resource> + </resource> + <resource id="Magento_Reports::report"> + <resource id="Magento_Analytics::business_intelligence" title="Business Intelligence" translate="title" sortOrder="90"> + <resource id="Magento_Analytics::advanced_reporting" title="Advanced Reporting" translate="title" sortOrder="10" /> + <resource id="Magento_Analytics::bi_essentials" title="BI Essentials" translate="title" sortOrder="20" /> + </resource> + </resource> + </resource> + </resources> + </acl> +</config> diff --git a/app/code/Magento/Analytics/etc/adminhtml/di.xml b/app/code/Magento/Analytics/etc/adminhtml/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..5e305e70e5ad3b63dd0e10e7651ce9717959ef62 --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/di.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\Notification\MessageList"> + <arguments> + <argument name="messages" xsi:type="array"> + <item name="analytics" xsi:type="string">Magento\Analytics\Model\System\Message\NotificationAboutFailedSubscription</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Analytics/etc/adminhtml/menu.xml b/app/code/Magento/Analytics/etc/adminhtml/menu.xml new file mode 100644 index 0000000000000000000000000000000000000000..915211c4bb85e728151f60e1738488b482417bee --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/menu.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd"> + <menu> + <add id="Magento_Analytics::business_intelligence" title="Business Intelligence" translate="title" module="Magento_Analytics" + sortOrder="90" parent="Magento_Reports::report" resource="Magento_Analytics::business_intelligence" /> + <add id="Magento_Analytics::advanced_reporting" title="Advanced Reporting" translate="title" module="Magento_Analytics" + sortOrder="10" parent="Magento_Analytics::business_intelligence" action="analytics/reports/show" + target="_blank" resource="Magento_Analytics::advanced_reporting" /> + <add id="Magento_Analytics::bi_essentials" title="BI Essentials" translate="title" module="Magento_Analytics" + sortOrder="20" parent="Magento_Analytics::business_intelligence" action="analytics/biessentials/signup" + target="_blank" resource="Magento_Analytics::bi_essentials" /> + </menu> +</config> diff --git a/app/code/Magento/Analytics/etc/adminhtml/routes.xml b/app/code/Magento/Analytics/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000000000000000000000000000000..0ae2762dacc5f01597dd930c41bdf7f04846e88d --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> + <router id="admin"> + <route id="analytics" frontName="analytics"> + <module name="Magento_Analytics" /> + </route> + </router> +</config> diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml new file mode 100644 index 0000000000000000000000000000000000000000..889517e629e046d217054911514eca7a5d6186b0 --- /dev/null +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="analytics" translate="label" type="text" sortOrder="1150" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Advanced Reporting</label> + <tab>general</tab> + <resource>Magento_Analytics::analytics_settings</resource> + <group id="general" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Advanced Reporting</label> + <comment><![CDATA[This service provides a dynamic suite of reports with rich insights about your business. + Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the + "Go to Advanced Reporting" link. </br> For more information, see our <a href="https://magento.com/legal/terms/cloud-terms"> + terms and conditions</a>.]]></comment> + <field id="enabled" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Advanced Reporting Service</label> + <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> + <backend_model>Magento\Analytics\Model\Config\Backend\Enabled</backend_model> + <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\SubscriptionStatusLabel</frontend_model> + <config_path>analytics/subscription/enabled</config_path> + </field> + <field id="collection_time" translate="label comment" type="time" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Time of day to send data</label> + <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel</frontend_model> + <backend_model>Magento\Analytics\Model\Config\Backend\CollectionTime</backend_model> + </field> + <field id="vertical" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <hint>Industry Data</hint> + <label>Industry</label> + <comment>In order to personalize your Advanced Reporting experience, please select your industry.</comment> + <source_model>Magento\Analytics\Model\Config\Source\Vertical</source_model> + <backend_model>Magento\Analytics\Model\Config\Backend\Vertical</backend_model> + <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\Vertical</frontend_model> + </field> + <field id="additional_comment" translate="label comment" type="label" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <label><![CDATA[<strong>Get more insights from Magento Business Intelligence</strong>]]></label> + <comment><![CDATA[Magento Business Intelligence provides you with a simple and clear path to + becoming more data driven.</br> Learn more about <a target="_blank" + href="https://dashboard.rjmetrics.com/v2/magento/signup/">Magento BI Essentials and BI Pro</a> tiers.]]></comment> + <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\AdditionalComment</frontend_model> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/Analytics/etc/analytics.xml b/app/code/Magento/Analytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..77ebe751a31cf8948c16e1994a5756514dbd61bb --- /dev/null +++ b/app/code/Magento/Analytics/etc/analytics.xml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="modules"> + <providers> + <reportProvider name="modules" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>modules</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="store_config"> + <providers> + <customProvider name="store_config" class="Magento\Analytics\Model\StoreConfigurationProvider"/> + </providers> + </file> + <file name="stores"> + <providers> + <reportProvider name="stores" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>stores</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="websites"> + <providers> + <reportProvider name="websites" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>websites</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="groups"> + <providers> + <reportProvider name="groups" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>groups</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/Analytics/etc/analytics.xsd b/app/code/Magento/Analytics/etc/analytics.xsd new file mode 100644 index 0000000000000000000000000000000000000000..2506e3d6a6a9a8b97de7e5ed8f7c62379ed0022d --- /dev/null +++ b/app/code/Magento/Analytics/etc/analytics.xsd @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="config"> + <xs:complexType> + <xs:sequence> + <xs:element name="file" type="fileDeclaration" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + <xs:unique name="uniqueFileName"> + <xs:selector xpath="file" /> + <xs:field xpath="@name" /> + </xs:unique> + </xs:element> + <xs:complexType name="fileDeclaration"> + <xs:sequence> + <xs:element name="providers" type="providers" minOccurs="1" /> + </xs:sequence> + <xs:attribute name="name" type="fileName" use="required" /> + <xs:attribute name="prefix" type="xs:string" /> + </xs:complexType> + <xs:complexType name="providers"> + <xs:choice> + <xs:sequence> + <xs:element name="reportProvider" type="reportProvider" /> + <xs:element name="customProvider" type="customProvider" minOccurs="0" /> + </xs:sequence> + <xs:element name="customProvider" type="customProvider" /> + </xs:choice> + </xs:complexType> + <xs:complexType name="provider" abstract="true"> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="class" type="xs:string" use="required"/> + </xs:complexType> + <xs:complexType name="reportProvider"> + <xs:complexContent> + <xs:extension base="provider"> + <xs:all> + <xs:element name="parameters"> + <xs:complexType> + <xs:sequence> + <xs:element name="name" type="notEmptyString" maxOccurs="1" /> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:all> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="customProvider"> + <xs:complexContent> + <xs:extension base="provider" /> + </xs:complexContent> + </xs:complexType> + <xs:simpleType name="fileName"> + <xs:annotation> + <xs:documentation> + File name attribute can has only [a-zA-Z0-9/_]. + </xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:pattern value="[a-zA-Z0-9/_]+" /> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="notEmptyString"> + <xs:annotation> + <xs:documentation> + Value is required. + </xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:minLength value="1" /> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/app/code/Magento/Analytics/etc/config.xml b/app/code/Magento/Analytics/etc/config.xml new file mode 100644 index 0000000000000000000000000000000000000000..b6194ba12993f005b53262152c8c2ec5f45e01c6 --- /dev/null +++ b/app/code/Magento/Analytics/etc/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <analytics> + <url> + <signup>https://advancedreporting.rjmetrics.com/signup</signup> + <update>https://advancedreporting.rjmetrics.com/update</update> + <bi_essentials>https://dashboard.rjmetrics.com/v2/magento/signup</bi_essentials> + <otp>https://advancedreporting.rjmetrics.com/otp</otp> + <report>https://advancedreporting.rjmetrics.com/report</report> + <notify_data_changed>https://advancedreporting.rjmetrics.com/report</notify_data_changed> + </url> + <integration_name>Magento Analytics user</integration_name> + <general> + <collection_time>02,00,00</collection_time> + </general> + </analytics> + </default> +</config> diff --git a/app/code/Magento/Analytics/etc/crontab.xml b/app/code/Magento/Analytics/etc/crontab.xml new file mode 100644 index 0000000000000000000000000000000000000000..a4beef0359540afefd6f15fc848856f007f8a31a --- /dev/null +++ b/app/code/Magento/Analytics/etc/crontab.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd"> + <group id="default"> + <job name="analytics_subscribe" instance="Magento\Analytics\Cron\SignUp" method="execute" /> + <job name="analytics_update" instance="Magento\Analytics\Cron\Update" method="execute" /> + <job name="analytics_collect_data" instance="Magento\Analytics\Cron\CollectData" method="execute" /> + </group> +</config> diff --git a/app/code/Magento/Analytics/etc/di.xml b/app/code/Magento/Analytics/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..56657b58475d30d4c810ead21db3c2264b9d2851 --- /dev/null +++ b/app/code/Magento/Analytics/etc/di.xml @@ -0,0 +1,257 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Analytics\ReportXML\ConfigInterface" type="Magento\Analytics\ReportXML\Config" /> + <preference for="Magento\Analytics\Model\ConfigInterface" type="Magento\Analytics\Model\Config" /> + <preference for="Magento\Analytics\Model\ReportWriterInterface" type="Magento\Analytics\Model\ReportWriter" /> + <preference for="Magento\Analytics\Api\LinkProviderInterface" type="Magento\Analytics\Model\LinkProvider" /> + <preference for="Magento\Analytics\Api\Data\LinkInterface" type="Magento\Analytics\Model\Link" /> + <preference for="Magento\Analytics\Model\Connector\Http\ClientInterface" type="Magento\Analytics\Model\Connector\Http\Client\Curl" /> + <preference for="Magento\Analytics\Model\ExportDataHandlerInterface" type="Magento\Analytics\Model\ExportDataHandlerNotification" /> + <preference for="Magento\Analytics\Model\Connector\Http\ConverterInterface" type="Magento\Analytics\Model\Connector\Http\JsonConverter" /> + <type name="Magento\Analytics\Model\Connector"> + <arguments> + <argument name="commands" xsi:type="array"> + <item name="signUp" xsi:type="string">Magento\Analytics\Model\Connector\SignUpCommand</item> + <item name="update" xsi:type="string">Magento\Analytics\Model\Connector\UpdateCommand</item> + <item name="notifyDataChanged" xsi:type="string">Magento\Analytics\Model\Connector\NotifyDataChangedCommand</item> + </argument> + </arguments> + </type> + <!--Configuration for \Magento\Analytics\ReportXml\Config--> + <type name="Magento\Analytics\ReportXml\Config"> + <arguments> + <argument name="data" xsi:type="object">Magento\Analytics\ReportXml\Config\Data</argument> + </arguments> + </type> + <virtualType name="Magento\Analytics\ReportXml\Config\Data" type="Magento\Framework\Config\Data"> + <arguments> + <argument name="reader" xsi:type="object">Magento\Analytics\ReportXml\Config\Reader</argument> + <argument name="cacheId" xsi:type="string">Magento_Analytics_ReportXml_CacheId</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Analytics\ReportXml\Config\SchemaLocator" type="Magento\Framework\Config\SchemaLocator"> + <arguments> + <argument name="realPath" xsi:type="string">urn:magento:module:Magento_Analytics:etc/reports.xsd</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Analytics\ReportXml\Config\Reader\Xml" type="Magento\Framework\Config\Reader\Filesystem"> + <arguments> + <argument name="converter" xsi:type="object">Magento\Analytics\ReportXml\Config\Converter\Xml</argument> + <argument name="schemaLocator" xsi:type="object">Magento\Analytics\ReportXml\Config\SchemaLocator</argument> + <argument name="fileName" xsi:type="string">reports.xml</argument> + <argument name="idAttributes" xsi:type="array"> + <item name="/config/report" xsi:type="string">name</item> + <item name="/config/report/source/link-source" xsi:type="array"> + <item name="name" xsi:type="string">name</item> + <item name="alias" xsi:type="string">alias</item> + </item> + <item name="/config/report/source/attribute" xsi:type="string">name</item> + <item name="/config/report/source/link-source/attribute" xsi:type="string">name</item> + <!-- filter conditions for main source--> + <item name="/config/report/source(/filter)+" xsi:type="string">glue</item> + <item name="/config/report/source(/filter)+/condition" xsi:type="array"> + <item name="attribute" xsi:type="string">attribute</item> + <item name="operator" xsi:type="string">operator</item> + </item> + <!-- filter conditions for joined source--> + <item name="/config/report/source/link-source(/filter)+" xsi:type="string">glue</item> + <item name="/config/report/source/link-source(/filter)+/condition" xsi:type="array"> + <item name="attribute" xsi:type="string">attribute</item> + <item name="operator" xsi:type="string">operator</item> + </item> + <!-- join conditions for joined source--> + <item name="/config/report/source/link-source/using" xsi:type="string">glue</item> + <item name="/config/report/source/link-source/using/condition" xsi:type="array"> + <item name="attribute" xsi:type="string">attribute</item> + <item name="operator" xsi:type="string">operator</item> + </item> + <item name="/config/report/source/link-source/using(/filter)+" xsi:type="string">glue</item> + <item name="/config/report/source/link-source/using(/filter)+/condition" xsi:type="array"> + <item name="attribute" xsi:type="string">attribute</item> + <item name="operator" xsi:type="string">operator</item> + </item> + </argument> + </arguments> + </virtualType> + <type name="Magento\Analytics\ReportXml\Config\Reader"> + <arguments> + <argument name="readers" xsi:type="array"> + <item name="xml" xsi:type="object">Magento\Analytics\ReportXml\Config\Reader\Xml</item> + </argument> + </arguments> + </type> + <!--Configuration for \Magento\Analytics\Model\Config--> + <type name="Magento\Analytics\Model\Config"> + <arguments> + <argument name="data" xsi:type="object">Magento\Analytics\Model\Config\Data</argument> + </arguments> + </type> + <virtualType name="Magento\Analytics\Model\Config\Data" type="Magento\Framework\Config\Data"> + <arguments> + <argument name="reader" xsi:type="object">Magento\Analytics\Model\Config\Reader</argument> + <argument name="cacheId" xsi:type="string">Magento_Analytics_CacheId</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Analytics\Model\Config\SchemaLocator" type="Magento\Framework\Config\SchemaLocator"> + <arguments> + <argument name="realPath" xsi:type="string">urn:magento:module:Magento_Analytics:etc/analytics.xsd</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Analytics\Model\Config\Reader\Xml" type="Magento\Framework\Config\Reader\Filesystem"> + <arguments> + <argument name="converter" xsi:type="object">Magento\Analytics\ReportXml\Config\Converter\Xml</argument> + <argument name="schemaLocator" xsi:type="object">Magento\Analytics\Model\Config\SchemaLocator</argument> + <argument name="fileName" xsi:type="string">analytics.xml</argument> + <argument name="idAttributes" xsi:type="array"> + <item name="/config/file" xsi:type="string">name</item> + </argument> + </arguments> + </virtualType> + <!-- --> + <type name="Magento\Analytics\ReportXml\QueryFactory"> + <arguments> + <argument name="assemblers" xsi:type="array"> + <item name="from" xsi:type="object">Magento\Analytics\ReportXml\DB\Assembler\FromAssembler</item> + <item name="filter" xsi:type="object">Magento\Analytics\ReportXml\DB\Assembler\FilterAssembler</item> + <item name="join" xsi:type="object">Magento\Analytics\ReportXml\DB\Assembler\JoinAssembler</item> + </argument> + </arguments> + </type> + <type name="Magento\Analytics\Model\Config\Reader"> + <arguments> + <argument name="readers" xsi:type="array"> + <item name="xml" xsi:type="object">Magento\Analytics\Model\Config\Reader\Xml</item> + </argument> + </arguments> + </type> + <type name="Magento\Analytics\Model\StoreConfigurationProvider"> + <arguments> + <argument name="configPaths" xsi:type="array"> + <item name="0" xsi:type="string">web/unsecure/base_url</item> + <item name="1" xsi:type="string">currency/options/base</item> + <item name="2" xsi:type="string">general/locale/timezone</item> + <item name="3" xsi:type="string">general/country/default</item> + <item name="4" xsi:type="string">carriers/dhl/title</item> + <item name="5" xsi:type="string">carriers/dhl/active</item> + <item name="6" xsi:type="string">carriers/fedex/title</item> + <item name="7" xsi:type="string">carriers/fedex/active</item> + <item name="8" xsi:type="string">carriers/flatrate/title</item> + <item name="9" xsi:type="string">carriers/flatrate/active</item> + <item name="10" xsi:type="string">carriers/tablerate/title</item> + <item name="11" xsi:type="string">carriers/tablerate/active</item> + <item name="12" xsi:type="string">carriers/freeshipping/title</item> + <item name="13" xsi:type="string">carriers/freeshipping/active</item> + <item name="14" xsi:type="string">carriers/ups/title</item> + <item name="15" xsi:type="string">carriers/ups/active</item> + <item name="16" xsi:type="string">carriers/usps/title</item> + <item name="17" xsi:type="string">carriers/usps/active</item> + <item name="18" xsi:type="string">payment/free/title</item> + <item name="19" xsi:type="string">payment/free/active</item> + <item name="20" xsi:type="string">payment/checkmo/title</item> + <item name="21" xsi:type="string">payment/checkmo/active</item> + <item name="22" xsi:type="string">payment/purchaseorder/title</item> + <item name="23" xsi:type="string">payment/purchaseorder/active</item> + <item name="24" xsi:type="string">payment/banktransfer/title</item> + <item name="25" xsi:type="string">payment/banktransfer/active</item> + <item name="26" xsi:type="string">payment/cashondelivery/title</item> + <item name="27" xsi:type="string">payment/cashondelivery/active</item> + <item name="28" xsi:type="string">payment/authorizenet_directpost/title</item> + <item name="29" xsi:type="string">payment/authorizenet_directpost/active</item> + <item name="30" xsi:type="string">payment/paypal_billing_agreement/title</item> + <item name="31" xsi:type="string">payment/paypal_billing_agreement/active</item> + <item name="32" xsi:type="string">payment/braintree/title</item> + <item name="33" xsi:type="string">payment/braintree/active</item> + <item name="34" xsi:type="string">payment/braintree_paypal/title</item> + <item name="35" xsi:type="string">payment/braintree_paypal/active</item> + <item name="36" xsi:type="string">analytics/general/vertical</item> + </argument> + </arguments> + </type> + <type name="Magento\Analytics\Model\Config\Source\Vertical"> + <arguments> + <argument name="verticals" xsi:type="array"> + <item name="0" xsi:type="string" translatable="true">Apps and Games</item> + <item name="1" xsi:type="string" translatable="true">Athletic/Sporting Goods</item> + <item name="2" xsi:type="string" translatable="true">Art and Design</item> + <item name="3" xsi:type="string" translatable="true">Auto Parts</item> + <item name="4" xsi:type="string" translatable="true">Baby/Children’s Apparel, Gear and Toys</item> + <item name="5" xsi:type="string" translatable="true">Beauty and Cosmetics</item> + <item name="6" xsi:type="string" translatable="true">Books, Music and Magazines</item> + <item name="7" xsi:type="string" translatable="true">Crafts and Stationery</item> + <item name="8" xsi:type="string" translatable="true">Consumer Electronics</item> + <item name="9" xsi:type="string" translatable="true">Deal Site</item> + <item name="10" xsi:type="string" translatable="true">Fashion Apparel and Accessories</item> + <item name="11" xsi:type="string" translatable="true">Food, Beverage and Grocery</item> + <item name="12" xsi:type="string" translatable="true">Home Goods and Furniture</item> + <item name="13" xsi:type="string" translatable="true">Home Improvement</item> + <item name="14" xsi:type="string" translatable="true">Jewelry and Watches</item> + <item name="15" xsi:type="string" translatable="true">Mass Merchant</item> + <item name="16" xsi:type="string" translatable="true">Office Supplies</item> + <item name="17" xsi:type="string" translatable="true">Outdoor and Camping Gear</item> + <item name="18" xsi:type="string" translatable="true">Pet Goods</item> + <item name="19" xsi:type="string" translatable="true">Pharma and Medical Devices</item> + <item name="20" xsi:type="string" translatable="true">Technology B2B</item> + <item name="21" xsi:type="string" translatable="true">Other</item> + </argument> + </arguments> + </type> + <type name="Magento\Config\Model\Config\Backend\Baseurl"> + <plugin name="updateAnalyticsSubscription" type="Magento\Analytics\Model\Plugin\BaseUrlConfigPlugin" /> + </type> + <virtualType name="SignUpResponseResolver" type="Magento\Analytics\Model\Connector\Http\ResponseResolver"> + <arguments> + <argument name="responseHandlers" xsi:type="array"> + <item name="201" xsi:type="object">\Magento\Analytics\Model\Connector\ResponseHandler\SignUp</item> + </argument> + </arguments> + </virtualType> + <virtualType name="UpdateResponseResolver" type="Magento\Analytics\Model\Connector\Http\ResponseResolver"> + <arguments> + <argument name="responseHandlers" xsi:type="array"> + <item name="201" xsi:type="object">Magento\Analytics\Model\Connector\ResponseHandler\Update</item> + <item name="401" xsi:type="object">Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp</item> + </argument> + </arguments> + </virtualType> + <virtualType name="OtpResponseResolver" type="Magento\Analytics\Model\Connector\Http\ResponseResolver"> + <arguments> + <argument name="responseHandlers" xsi:type="array"> + <item name="201" xsi:type="object">Magento\Analytics\Model\Connector\ResponseHandler\OTP</item> + <item name="401" xsi:type="object">Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp</item> + </argument> + </arguments> + </virtualType> + <virtualType name="NotifyDataChangedResponseResolver" type="Magento\Analytics\Model\Connector\Http\ResponseResolver"> + <arguments> + <argument name="responseHandlers" xsi:type="array"> + <item name="401" xsi:type="object">Magento\Analytics\Model\Connector\ResponseHandler\ReSignUp</item> + </argument> + </arguments> + </virtualType> + <type name="Magento\Analytics\Model\Connector\SignUpCommand"> + <arguments> + <argument name="responseResolver" xsi:type="object">SignUpResponseResolver</argument> + </arguments> + </type> + <type name="Magento\Analytics\Model\Connector\UpdateCommand"> + <arguments> + <argument name="responseResolver" xsi:type="object">UpdateResponseResolver</argument> + </arguments> + </type> + <type name="Magento\Analytics\Model\Connector\OTPRequest"> + <arguments> + <argument name="responseResolver" xsi:type="object">OtpResponseResolver</argument> + </arguments> + </type> + <type name="Magento\Analytics\Model\Connector\NotifyDataChangedCommand"> + <arguments> + <argument name="responseResolver" xsi:type="object">NotifyDataChangedResponseResolver</argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Analytics/etc/module.xml b/app/code/Magento/Analytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..32ee5d23a4d866aeef88a2ef62bedab3d085b355 --- /dev/null +++ b/app/code/Magento/Analytics/etc/module.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_Analytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Integration"/> + <module name="Magento_Backend"/> + <module name="Magento_Store"/> + <module name="Magento_Config"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/Analytics/etc/reports.xml b/app/code/Magento/Analytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..8a4365867029358a7f9a243492840b5845339764 --- /dev/null +++ b/app/code/Magento/Analytics/etc/reports.xml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="modules" connection="default" iterator="Magento\Analytics\Model\ReportXml\ModuleIterator"> + <source name="setup_module"> + <attribute name="module" alias="module_name"/> + <attribute name="schema_version"/> + <attribute name="data_version"/> + </source> + </report> + <report name="config_data" connection="default"> + <source name="core_config_data"> + <attribute name="path"/> + <attribute name="value"/> + </source> + </report> + <report name="stores" connection="default"> + <source name="store"> + <attribute name="store_id"/> + <attribute name="code"/> + <attribute name="group_id"/> + <attribute name="name"/> + <attribute name="is_active"/> + </source> + </report> + <report name="websites" connection="default"> + <source name="store_website"> + <attribute name="website_id"/> + <attribute name="code"/> + <attribute name="name"/> + <attribute name="default_group_id"/> + <attribute name="is_default"/> + </source> + </report> + <report name="groups" connection="default"> + <source name="store_group"> + <attribute name="group_id"/> + <attribute name="website_id"/> + <attribute name="name"/> + <attribute name="default_store_id"/> + </source> + </report> +</config> \ No newline at end of file diff --git a/app/code/Magento/Analytics/etc/reports.xsd b/app/code/Magento/Analytics/etc/reports.xsd new file mode 100644 index 0000000000000000000000000000000000000000..d0ba4068244fe966cbd666326db4f6fd40cdf4f1 --- /dev/null +++ b/app/code/Magento/Analytics/etc/reports.xsd @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="config"> + <xs:complexType> + <xs:sequence> + <xs:element name="report" type="reportDeclaration" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:complexType name="reportDeclaration"> + <xs:sequence> + <xs:element name="source" type="sourceDeclaration" minOccurs="1" maxOccurs="1" /> + <xs:any minOccurs="0"/> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="connection" type="xs:string"/> + <xs:attribute name="iterator" type="xs:string"/> + </xs:complexType> + <xs:complexType name="sourceDeclaration"> + <xs:choice minOccurs="1" maxOccurs="unbounded"> + <xs:element name="attribute" type="attributeDeclaration" minOccurs="1" maxOccurs="unbounded" /> + <xs:element name="link-source" type="linkSourceDeclaration" minOccurs="0" maxOccurs="61" /> + <xs:element name="filter" type="filterDeclaration" minOccurs="0" maxOccurs="unbounded" /> + </xs:choice> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="alias" type="xs:string"/> + </xs:complexType> + <xs:complexType name="linkSourceDeclaration"> + <xs:choice minOccurs="1" maxOccurs="unbounded"> + <xs:element name="attribute" type="attributeDeclaration" minOccurs="0" maxOccurs="unbounded" /> + <xs:element name="filter" type="filterDeclaration" minOccurs="0" maxOccurs="unbounded" /> + <xs:element name="using" type="filterDeclaration" minOccurs="1" maxOccurs="unbounded" /> + </xs:choice> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="alias" type="xs:string"/> + <xs:attribute name="link-type" type="xs:string"/> + </xs:complexType> + <xs:complexType name="attributeDeclaration"> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="alias" type="xs:string"/> + <xs:attribute name="function" type="functionDeclaration"/> + <xs:attribute name="group" type="xs:boolean" default="false"/> + <xs:attribute name="distinct" type="xs:boolean" default="false"/> + </xs:complexType> + <xs:complexType name="filterDeclaration"> + <xs:choice minOccurs="1" maxOccurs="unbounded"> + <xs:element name="filter" type="filterDeclaration" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="condition" type="conditionDeclaration" minOccurs="1" maxOccurs="unbounded" /> + </xs:choice> + <xs:attribute name="glue" type="glueType" default="and" /> + </xs:complexType> + <xs:complexType name="conditionDeclaration" mixed="true"> + <xs:attribute name="attribute" type="xs:string" use="required" /> + <xs:attribute name="operator" type="xs:string" use="required" /> + <xs:attribute name="type" type="valueType" default="value" /> + </xs:complexType> + <xs:simpleType name="valueType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="value" /> + <xs:enumeration value="variable" /> + <xs:enumeration value="identifier" /> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="functionDeclaration"> + <xs:restriction base="xs:string"> + <xs:enumeration value="count" /> + <xs:enumeration value="lower" /> + <xs:enumeration value="date" /> + <xs:enumeration value="sum" /> + <xs:enumeration value="max" /> + <xs:enumeration value="avg" /> + <xs:enumeration value="min" /> + <xs:enumeration value="sha1" /> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="glueType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="and" /> + <xs:enumeration value="or" /> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/app/code/Magento/Analytics/etc/webapi.xml b/app/code/Magento/Analytics/etc/webapi.xml new file mode 100644 index 0000000000000000000000000000000000000000..8252d039f1d03081396d836d640f47cd1ed52802 --- /dev/null +++ b/app/code/Magento/Analytics/etc/webapi.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"> + <route url="/V1/analytics/link" method="GET" secure="true"> + <service class="Magento\Analytics\Api\LinkProviderInterface" method="get"/> + <resources> + <resource ref="Magento_Analytics::analytics_api" /> + </resources> + </route> +</routes> diff --git a/app/code/Magento/Analytics/i18n/en_US.csv b/app/code/Magento/Analytics/i18n/en_US.csv new file mode 100644 index 0000000000000000000000000000000000000000..516c388feb8239ad2a18aedaf046752281cea8b8 --- /dev/null +++ b/app/code/Magento/Analytics/i18n/en_US.csv @@ -0,0 +1,84 @@ +"Subscription status","Subscription status" +"Sorry, there has been an error processing your request. Please try again later.","Sorry, there has been an error processing your request. Please try again later." +"Sorry, there was an error processing your registration request to Magento Analytics. Please try again later.","Sorry, there was an error processing your registration request to Magento Analytics. Please try again later." +"Error occurred during postponement notification","Error occurred during postponement notification" +"Time value has an unsupported format","Time value has an unsupported format" +"Cron settings can't be saved","Cron settings can't be saved" +"There was an error save new configuration value.","There was an error save new configuration value." +"Please select a vertical.","Please select a vertical." +"--Please Select--","--Please Select--" +"Command was not found.","Command was not found." +"Input data must be string or convertible into string.","Input data must be string or convertible into string." +"Input data must be non-empty string.","Input data must be non-empty string." +"Not valid cipher method.","Not valid cipher method." +"Encryption key can't be empty.","Encryption key can't be empty." +"Source ""%1"" is not exist","Source ""%1"" is not exist" +"These arguments can't be empty ""%1""","These arguments can't be empty ""%1""" +"Cannot find predefined integration user!","Cannot find predefined integration user!" +"File is not ready yet.","File is not ready yet." +"Your Base URL has been changed and your reports are being updated. Advanced Reporting will be available once this change has been processed. Please try again later.","Your Base URL has been changed and your reports are being updated. Advanced Reporting will be available once this change has been processed. Please try again later." +"Failed to synchronize data to the Magento Business Intelligence service. ","Failed to synchronize data to the Magento Business Intelligence service. " +"<a href=""%1"">Retry Synchronization</a>","<a href=""%1"">Retry Synchronization</a>" +TestMessage,TestMessage +"Error message","Error message" +"Apps and Games","Apps and Games" +"Athletic/Sporting Goods","Athletic/Sporting Goods" +"Art and Design","Art and Design" +"Advanced Reporting","Advanced Reporting" +"Gain new insights and take command of your business' performance, using our dynamic product, order, and customer reports tailored to your customer data.","Gain new insights and take command of your business' performance, using our dynamic product, order, and customer reports tailored to your customer data." +"View details","View details" +"Go to Advanced Reporting","Go to Advanced Reporting" +"An error occurred while subscription process.","An error occurred while subscription process." +Analytics,Analytics +API,API +Configuration,Configuration +"Business Intelligence","Business Intelligence" +"BI Essentials","BI Essentials" +"This service provides a dynamic suite of reports with rich insights about your business. + Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the + ""Go to Advanced Reporting"" link. </br> For more information, see our + <a href=""https://magento.com/legal/terms/privacy"">terms and conditions</a>. + ","This service provides a dynamic suite of reports with rich insights about your business. + Your reports can be accessed securely on a personalized dashboard outside of the admin panel by clicking on the + ""Go to Advanced Reporting"" link. </br> For more information, see our + <a href=""https://magento.com/legal/terms/privacy"">terms and conditions</a>." +"Advanced Reporting Service","Advanced Reporting Service" +Industry,Industry +"Time of day to send data","Time of day to send data" +"<strong>Get more insights from Magento Business Intelligence</strong>","<strong>Get more insights from Magento Business Intelligence</strong>" +"Magento Business Intelligence provides you with a simple and clear path to + becoming more data driven.</br> Learn more about <a target=""_blank"" + href=""https://dashboard.rjmetrics.com/v2/magento/signup/"">BI Essentials</a> tier.","Magento Business Intelligence provides you with a simple and clear path to + becoming more data driven.</br> Learn more about <a target=""_blank"" + href=""https://dashboard.rjmetrics.com/v2/magento/signup/"">BI Essentials</a> tier." +"Auto Parts","Auto Parts" +"Baby/Children’s Apparel, Gear and Toys","Baby/Children’s Apparel, Gear and Toys" +"Beauty and Cosmetics","Beauty and Cosmetics" +"Books, Music and Magazines","Books, Music and Magazines" +"Crafts and Stationery","Crafts and Stationery" +"Consumer Electronics","Consumer Electronics" +"Deal Site","Deal Site" +"Fashion Apparel and Accessories","Fashion Apparel and Accessories" +"Food, Beverage and Grocery","Food, Beverage and Grocery" +"Home Goods and Furniture","Home Goods and Furniture" +"Home Improvement","Home Improvement" +"Jewelry and Watches","Jewelry and Watches" +"Mass Merchant","Mass Merchant" +"Office Supplies","Office Supplies" +"Outdoor and Camping Gear","Outdoor and Camping Gear" +"Pet Goods","Pet Goods" +"Pharma and Medical Devices","Pharma and Medical Devices" +"Technology B2B","Technology B2B" +"Analytics Subscription","Analytics Subscription" +"powered by Magento Business Intelligence","powered by Magento Business Intelligence" +"Are you sure you want to opt out?","Are you sure you want to opt out?" +Cancel,Cancel +"Opt out","Opt out" +"<p>Advanced Reporting in included, + free of charge, in your Magento software. When you opt out, we collect no product, order, and + customer data to generate our dynamic reports.</p><p>To opt in later: You can always turn on Advanced + Reporting in you Admin Panel.</p>","<p>Advanced Reporting in included, + free of charge, in your Magento software. When you opt out, we collect no product, order, and + customer data to generate our dynamic reports.</p><p>To opt in later: You can always turn on Advanced + Reporting in you Admin Panel.</p>" +"In order to personalize your Advanced Reporting experience, please select your industry.","In order to personalize your Advanced Reporting experience, please select your industry." diff --git a/app/code/Magento/Analytics/registration.php b/app/code/Magento/Analytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..58d3688b7491de104599378301b89c8c92271445 --- /dev/null +++ b/app/code/Magento/Analytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_Analytics', + __DIR__ +); diff --git a/app/code/Magento/Analytics/view/adminhtml/layout/adminhtml_dashboard_index.xml b/app/code/Magento/Analytics/view/adminhtml/layout/adminhtml_dashboard_index.xml new file mode 100644 index 0000000000000000000000000000000000000000..545098de52dcc5295d48a29513b277501a5d8346 --- /dev/null +++ b/app/code/Magento/Analytics/view/adminhtml/layout/adminhtml_dashboard_index.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="content"> + <block template="Magento_Analytics::dashboard/section.phtml" + class="Magento\Backend\Block\Template" + name="analytics_service_external_link" + before="-"/> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml new file mode 100644 index 0000000000000000000000000000000000000000..a22c603b2a8b335eff2be172f5a347449dd7b397 --- /dev/null +++ b/app/code/Magento/Analytics/view/adminhtml/templates/dashboard/section.phtml @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +// @codingStandardsIgnoreFile +?> + +<section class="dashboard-advanced-reports" data-index="dashboard-advanced-reports"> + <div class="dashboard-advanced-reports-description"> + <header class="dashboard-advanced-reports-title"> + <?= $block->escapeHtml(__('Advanced Reporting')) ?> + </header> + <div class="dashboard-advanced-reports-content"> + <?= $block->escapeHtml(__('Gain new insights and take command of your business\' performance,' . + ' using our dynamic product, order, and customer reports tailored to your customer data.')) ?> + </div> + </div> + <div class="dashboard-advanced-reports-actions"> + <a href="<?= $block->escapeUrl($block->getUrl('analytics/reports/show')) ?>" + target="_blank" + class="action action-advanced-reports" + data-index="analytics-service-link" + title="<?= $block->escapeHtmlAttr(__('Go to Advanced Reporting')) ?>"> + <span><?= $block->escapeHtml(__('Go to Advanced Reporting')) ?></span> + </a> + </div> +</section> diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index 2730d4d92835bbbb1cc950983a99b0d65ef50873..f9f44f547e25b38dd98f4e93edd89de196315520 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -214,10 +214,13 @@ YTD,YTD "Admin session lifetime must be greater than or equal to 60 seconds","Admin session lifetime must be greater than or equal to 60 seconds" Order,Order "Order #%1","Order #%1" -"Access denied","Access denied" -"Please try to sign out and sign in again.","Please try to sign out and sign in again." -"If you continue to receive this message, please contact the store owner.","If you continue to receive this message, please contact the store owner." "You need more permissions to access this.","You need more permissions to access this." +"Sorry, you need permissions to view this content.","Sorry, you need permissions to view this content." +"Next steps","Next steps" +"If you think this is an error, try signing out and signing in again.","If you think this is an error, try signing out and signing in again." +"Contact a system administrator or store owner to gain permissions.","Contact a system administrator or store owner to gain permissions." +"Return to","Return to" +"previous page","previous page" "Welcome, please sign in","Welcome, please sign in" Username,Username "user name","user name" diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml index 852ecd5a07962ebf6c8ea2ea30289aaddbc8cf45..4db99b3ed5de7975cf360e61b507c057d2d611c2 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml @@ -9,15 +9,21 @@ ?> <?php /** - * @see \Magento\Backend\Block\Denied + * @see \Magento\Backend\Block\Template */ ?> -<h1 class="page-heading"><?= /* @escapeNotVerified */ __('Access denied') ?></h1> -<?php if (!$block->hasAvailableResources()): ?> -<p> -<?= /* @escapeNotVerified */ __('Please try to sign out and sign in again.') ?><br/> -<?= /* @escapeNotVerified */ __('If you continue to receive this message, please contact the store owner.') ?> -</p> -<?php else: ?> -<p><?= /* @escapeNotVerified */ __('You need more permissions to access this.') ?></p> -<?php endif?> +<hr class="access-denied-hr"/> +<div class="access-denied-page"> + <h2 class="page-heading"><?= $block->escapeHtml(__('Sorry, you need permissions to view this content.')) ?></h2> + <strong><?= $block->escapeHtml(__('Next steps')) ?></strong> + <ul> + <li><span><?= $block->escapeHtml(__('If you think this is an error, try signing out and signing in again.')) ?></span></li> + <li><span><?= $block->escapeHtml(__('Contact a system administrator or store owner to gain permissions.')) ?></span></li> + <li> + <span><?= $block->escapeHtml(__('Return to ')) ?> + <a href="<?= $block->escapeHtmlAttr(__('javascript:history.back()')) ?>"> + <?= $block->escapeHtml(__('previous page')) ?></a><?= $block->escapeHtml(__('.')) ?> + </span> + </li> + </ul> +</div> diff --git a/app/code/Magento/CatalogAnalytics/LICENSE.txt b/app/code/Magento/CatalogAnalytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/CatalogAnalytics/LICENSE_AFL.txt b/app/code/Magento/CatalogAnalytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/CatalogAnalytics/README.md b/app/code/Magento/CatalogAnalytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..df125446117a3e6c3e7e73e0c1220b6cf50a4515 --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/README.md @@ -0,0 +1,3 @@ +# Magento_CatalogAnalytics module + +The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..6cda52197f80a0ccb9fd5d7f4fc61c7ac0013f1c --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-catalog-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-catalog": "101.1.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\CatalogAnalytics\\": "" + } + } +} diff --git a/app/code/Magento/CatalogAnalytics/etc/analytics.xml b/app/code/Magento/CatalogAnalytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..22d1f2c7d7776a0fdd87c3dd58f4855bf7802e6b --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/etc/analytics.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="products"> + <providers> + <reportProvider name="products" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>products</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/CatalogAnalytics/etc/module.xml b/app/code/Magento/CatalogAnalytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..7974598e17a5907fd2f30a892e0c696223152262 --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_CatalogAnalytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Catalog"/> + <module name="Magento_Analytics"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/CatalogAnalytics/etc/reports.xml b/app/code/Magento/CatalogAnalytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..5dae3ef90d7b273480b8070d832592cc035ce0d4 --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/etc/reports.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="products" connection="default"> + <source name="catalog_product_entity"> + <attribute name="entity_id"/> + <attribute name="sku"/> + </source> + </report> +</config> diff --git a/app/code/Magento/CatalogAnalytics/registration.php b/app/code/Magento/CatalogAnalytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..77d6ce154b658a47f923bd004642800f527f1bb3 --- /dev/null +++ b/app/code/Magento/CatalogAnalytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_CatalogAnalytics', + __DIR__ +); diff --git a/app/code/Magento/CustomerAnalytics/LICENSE.txt b/app/code/Magento/CustomerAnalytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/CustomerAnalytics/LICENSE_AFL.txt b/app/code/Magento/CustomerAnalytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/CustomerAnalytics/README.md b/app/code/Magento/CustomerAnalytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8c64ce97629da048715d4df6d20a375f374eed3e --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/README.md @@ -0,0 +1,3 @@ +# Magento_CustomerAnalytics module + +The Magento_CustomerAnalytics module configures data definitions for a data collection related to the Customer module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/CustomerAnalytics/composer.json b/app/code/Magento/CustomerAnalytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..36e7492decc50fdaef50668ad2059820632dcc47 --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-customer-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-customer": "100.2.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\CustomerAnalytics\\": "" + } + } +} diff --git a/app/code/Magento/CustomerAnalytics/etc/analytics.xml b/app/code/Magento/CustomerAnalytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..5e47040c2f3bd897d6d00a153c8c926fb7eeea9b --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/etc/analytics.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="customers"> + <providers> + <reportProvider name="customers" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>customers</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/CustomerAnalytics/etc/module.xml b/app/code/Magento/CustomerAnalytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..adc4f8dd849c2adba8e61a89a49cf6ea4b9a8737 --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_CustomerAnalytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Customer"/> + <module name="Magento_Analytics"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/CustomerAnalytics/etc/reports.xml b/app/code/Magento/CustomerAnalytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..b3300b0127709d4d48d7fb1b6b96697a678fc452 --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/etc/reports.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="customers" connection="default"> + <source name="customer_entity"> + <attribute name="entity_id"/> + <attribute name="created_at"/> + <attribute name="email" function="sha1"/> + <attribute name="store_id"/> + </source> + </report> +</config> diff --git a/app/code/Magento/CustomerAnalytics/registration.php b/app/code/Magento/CustomerAnalytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..e4c33481828777cd07fd20eca2d571f2d95da6f6 --- /dev/null +++ b/app/code/Magento/CustomerAnalytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_CustomerAnalytics', + __DIR__ +); diff --git a/app/code/Magento/QuoteAnalytics/LICENSE.txt b/app/code/Magento/QuoteAnalytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/QuoteAnalytics/LICENSE_AFL.txt b/app/code/Magento/QuoteAnalytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/QuoteAnalytics/README.md b/app/code/Magento/QuoteAnalytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d4adcc9313229b5d435acbcade90e0af734c99da --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/README.md @@ -0,0 +1,3 @@ +# Magento_QuoteAnalytics + +The Magento_QuoteAnalytics module configures data definitions for a data collection related to the Quote module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/QuoteAnalytics/composer.json b/app/code/Magento/QuoteAnalytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..7f38e489ab0b0f5131c4ee665a03f1d8a49fb052 --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-quote-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-quote": "100.2.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\QuoteAnalytics\\": "" + } + } +} diff --git a/app/code/Magento/QuoteAnalytics/etc/analytics.xml b/app/code/Magento/QuoteAnalytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..cc4dfb636490422b539987d1b10837d4d8333ed7 --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/etc/analytics.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="quotes"> + <providers> + <reportProvider name="quotes" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>quotes</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/QuoteAnalytics/etc/module.xml b/app/code/Magento/QuoteAnalytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..d72e36b7487488f0507fb8fca55f55fdc395b8bb --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_QuoteAnalytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Quote"/> + <module name="Magento_Analytics"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/QuoteAnalytics/etc/reports.xml b/app/code/Magento/QuoteAnalytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..f57012df233890beaa94a264e77a7843f41c51f9 --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/etc/reports.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="quotes" connection="default"> + <source name="quote"> + <attribute name="entity_id"/> + <attribute name="customer_id"/> + <attribute name="store_id"/> + <attribute name="created_at"/> + <attribute name="converted_at"/> + <attribute name="is_active"/> + <attribute name="items_count"/> + <attribute name="items_qty"/> + <attribute name="orig_order_id"/> + </source> + </report> +</config> diff --git a/app/code/Magento/QuoteAnalytics/registration.php b/app/code/Magento/QuoteAnalytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..19718c3cf2adf3780eb58b070cffd37a3b2c59ab --- /dev/null +++ b/app/code/Magento/QuoteAnalytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_QuoteAnalytics', + __DIR__ +); diff --git a/app/code/Magento/ReleaseNotification/Controller/Adminhtml/Notification/MarkUserNotified.php b/app/code/Magento/ReleaseNotification/Controller/Adminhtml/Notification/MarkUserNotified.php new file mode 100644 index 0000000000000000000000000000000000000000..572fb7695b20f1ec957b6ffb8d55bc92022a82f4 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Controller/Adminhtml/Notification/MarkUserNotified.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ReleaseNotification\Controller\Adminhtml\Notification; + +use Magento\Backend\App\Action; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Controller\ResultFactory; +use Magento\ReleaseNotification\Model\ResourceModel\Viewer\Logger as NotificationLogger; +use Magento\Framework\App\ProductMetadataInterface; +use Psr\Log\LoggerInterface; + +/** + * Class MarkUserNotified + */ +class MarkUserNotified extends Action +{ + /** + * @var ProductMetadataInterface + */ + private $productMetadata; + + /** + * @var NotificationLogger + */ + private $notificationLogger; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * MarkUserNotified constructor. + * + * @param Action\Context $context + * @param ProductMetadataInterface $productMetadata + * @param NotificationLogger $notificationLogger + * @param LoggerInterface $logger + */ + public function __construct( + Action\Context $context, + ProductMetadataInterface $productMetadata, + NotificationLogger $notificationLogger, + LoggerInterface $logger + ) { + parent::__construct($context); + $this->productMetadata = $productMetadata; + $this->notificationLogger = $notificationLogger; + $this->logger = $logger; + } + + /** + * Log information about the last shown advertisement + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + try { + $responseContent = [ + 'success' => $this->notificationLogger->log( + $this->_auth->getUser()->getId(), + $this->productMetadata->getVersion() + ), + 'error_message' => '' + ]; + } catch (LocalizedException $e) { + $this->logger->error($e->getMessage()); + $responseContent = [ + 'success' => false, + 'error_message' => $e->getMessage() + ]; + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $responseContent = [ + 'success' => false, + 'error_message' => __('It is impossible to log user action') + ]; + } + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + return $resultJson->setData($responseContent); + } + + protected function _isAllowed() + { + return parent::_isAllowed(); + } +} diff --git a/app/code/Magento/ReleaseNotification/LICENSE.txt b/app/code/Magento/ReleaseNotification/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/ReleaseNotification/LICENSE_AFL.txt b/app/code/Magento/ReleaseNotification/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php b/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php new file mode 100644 index 0000000000000000000000000000000000000000..07e26bb1a4d8dfc60a575bb3c6f04bd593b742d3 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Model/Condition/CanViewNotification.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Model\Condition; + +use Magento\ReleaseNotification\Model\ResourceModel\Viewer\Logger; +use Magento\Backend\Model\Auth\Session; +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Framework\View\Layout\Condition\VisibilityConditionInterface; +use Magento\Framework\App\CacheInterface; + +/** + * Class CanViewNotification + * + * Dynamic validator for UI release notification, manage UI component visibility. + * Return true if the logged in user has not seen the notification. + */ +class CanViewNotification implements VisibilityConditionInterface +{ + /** + * Unique condition name. + * + * @var string + */ + private static $conditionName = 'can_view_notification'; + + /** + * Prefix for cache + * + * @var string + */ + private static $cachePrefix = 'release-notification-popup-'; + + /** + * @var Logger + */ + private $viewerLogger; + + /** + * @var Session + */ + private $session; + + /** + * @var ProductMetadataInterface + */ + private $productMetadata; + + /** + * @var CacheInterface + */ + private $cacheStorage; + + /** + * CanViewNotification constructor. + * + * @param Logger $viewerLogger + * @param Session $session + * @param ProductMetadataInterface $productMetadata + * @param CacheInterface $cacheStorage + */ + public function __construct( + Logger $viewerLogger, + Session $session, + ProductMetadataInterface $productMetadata, + CacheInterface $cacheStorage + ) { + $this->viewerLogger = $viewerLogger; + $this->session = $session; + $this->productMetadata = $productMetadata; + $this->cacheStorage = $cacheStorage; + } + + /** + * Validate if notification popup can be shown and set the notification flag + * + * @inheritdoc + */ + public function isVisible(array $arguments) + { + $userId = $this->session->getUser()->getId(); + $cacheKey = self::$cachePrefix . $userId; + $value = $this->cacheStorage->load($cacheKey); + if ($value === false) { + $value = version_compare( + $this->viewerLogger->get($userId)->getLastViewVersion(), + $this->productMetadata->getVersion(), + '<' + ); + $this->cacheStorage->save(false, $cacheKey); + } + return (bool)$value; + } + + /** + * Get condition name + * + * @return string + */ + public function getName() + { + return self::$conditionName; + } +} diff --git a/app/code/Magento/ReleaseNotification/Model/ResourceModel/Viewer/Logger.php b/app/code/Magento/ReleaseNotification/Model/ResourceModel/Viewer/Logger.php new file mode 100644 index 0000000000000000000000000000000000000000..967ccabcdb49c267262e5d8cb43dbd475584a1f5 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Model/ResourceModel/Viewer/Logger.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ReleaseNotification\Model\ResourceModel\Viewer; + +use Magento\ReleaseNotification\Model\Viewer\Log; +use Magento\ReleaseNotification\Model\Viewer\LogFactory; +use Magento\Framework\App\ResourceConnection; + +/** + * Release notification viewer log data logger. + * + * Saves and retrieves release notification viewer log data. + */ +class Logger +{ + /** + * Log table name + */ + const LOG_TABLE_NAME = 'release_notification_viewer_log'; + + /** + * @var Resource + */ + private $resource; + + /** + * @var LogFactory + */ + private $logFactory; + + /** + * Logger constructor. + * @param ResourceConnection $resource + * @param LogFactory $logFactory + */ + public function __construct( + ResourceConnection $resource, + LogFactory $logFactory + ) { + $this->resource = $resource; + $this->logFactory = $logFactory; + } + + /** + * Save (insert new or update existing) log. + * + * @param int $viewerId + * @param string $lastViewVersion + * @return bool + */ + public function log(int $viewerId, string $lastViewVersion) : bool + { + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $connection */ + $connection = $this->resource->getConnection(ResourceConnection::DEFAULT_CONNECTION); + $connection->insertOnDuplicate( + $this->resource->getTableName(self::LOG_TABLE_NAME), + [ + 'viewer_id' => $viewerId, + 'last_view_version' => $lastViewVersion + ], + [ + 'last_view_version' + ] + ); + return true; + } + + /** + * Get log by viewer Id. + * + * @param int $viewerId + * @return Log + */ + public function get(int $viewerId) : Log + { + return $this->logFactory->create(['data' => $this->loadLogData($viewerId)]); + } + + /** + * Load release notification viewer log data by viewer id + * + * @param int $viewerId + * @return array + */ + private function loadLogData(int $viewerId) : array + { + $connection = $this->resource->getConnection(); + $select = $connection->select() + ->from($this->resource->getTableName(self::LOG_TABLE_NAME)) + ->where('viewer_id = ?', $viewerId); + + $data = $connection->fetchRow($select); + if (!$data) { + $data = []; + } + return $data; + } +} diff --git a/app/code/Magento/ReleaseNotification/Model/Viewer/Log.php b/app/code/Magento/ReleaseNotification/Model/Viewer/Log.php new file mode 100644 index 0000000000000000000000000000000000000000..27100b62fa798098f43f18067359fdbdb7be2659 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Model/Viewer/Log.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Model\Viewer; + +use Magento\Framework\DataObject; + +/** + * Class Log + * + * Release notification viewer log resource + */ +class Log extends DataObject +{ + /** + * Get log id + * + * @return int + */ + public function getId() + { + return $this->getData('id'); + } + + /** + * Get viewer id + * + * @return int + */ + public function getViewerId() + { + return $this->getData('viewer_id'); + } + + /** + * Get last viewed product version + * + * @return string + */ + public function getLastViewVersion() + { + return $this->getData('last_view_version'); + } +} diff --git a/app/code/Magento/ReleaseNotification/README.md b/app/code/Magento/ReleaseNotification/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bb0a6e7f4cf80058bab5e950405b8b6794859f6e --- /dev/null +++ b/app/code/Magento/ReleaseNotification/README.md @@ -0,0 +1,11 @@ + # Magento_ReleaseNotification Module + +The **Release Notification Module** serves to provide a notification delivery platform for displaying new features of a Magento installation or upgrade as well as any other required release notifications. + +## Purpose and Content + +* Provides a method of notifying administrators of changes, features, and functionality being introduced in a Magento release +* Displays a modal containing a high level overview of the features included in the installed or upgraded release of Magento upon the initial login of each administrator into the Admin Panel for a given Magento version +* The modal is enabled with pagination functionality to allow for easy navigation between each modal page +* Each modal page includes detailed information about a highlighted feature of the Magento release or other notification +* Release Notification modal content is determined and provided by Magento Marketing diff --git a/app/code/Magento/ReleaseNotification/Setup/InstallSchema.php b/app/code/Magento/ReleaseNotification/Setup/InstallSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..a6a0f51befa3fe08e7a9d7cc7e29a73b22d931c5 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Setup/InstallSchema.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Setup; + +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\Setup\InstallSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * @codeCoverageIgnore + */ +class InstallSchema implements InstallSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + /** + * Create table 'release_notification_viewer_log' + */ + $table = $setup->getConnection()->newTable( + $setup->getTable('release_notification_viewer_log') + )->addColumn( + 'id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Log ID' + )->addColumn( + 'viewer_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false], + 'Viewer admin user ID' + )->addColumn( + 'last_view_version', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 16, + ['nullable' => false], + 'Viewer last view on product version' + )->addIndex( + $setup->getIdxName( + 'release_notification_viewer_log', + ['viewer_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['viewer_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + )->addForeignKey( + $setup->getFkName('release_notification_viewer_log', 'viewer_id', 'admin_user', 'user_id'), + 'viewer_id', + $setup->getTable('admin_user'), + 'user_id', + Table::ACTION_CASCADE + )->setComment( + 'Release Notification Viewer Log Table' + ); + $setup->getConnection()->createTable($table); + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/ReleaseNotification/Test/Unit/Controller/Notification/MarkUserNotifiedTest.php b/app/code/Magento/ReleaseNotification/Test/Unit/Controller/Notification/MarkUserNotifiedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..894368cbcba01e5fd371111c201fb9d2efc6a349 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Test/Unit/Controller/Notification/MarkUserNotifiedTest.php @@ -0,0 +1,189 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ReleaseNotification\Test\Unit\Controller\Notification; + +use Psr\Log\LoggerInterface; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\Auth\Credential\StorageInterface; +use Magento\Backend\Model\Auth; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\ReleaseNotification\Model\ResourceModel\Viewer\Logger as NotificationLogger; +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\ReleaseNotification\Controller\Adminhtml\Notification\MarkUserNotified; + +/** + * Class MarkUserNotifiedTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MarkUserNotifiedTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|StorageInterface + */ + private $storageMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Auth + */ + private $authMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|LoggerInterface + */ + private $loggerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Json + */ + private $resultMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|ProductMetadataInterface + */ + private $productMetadataMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|NotificationLogger + */ + private $notificationLoggerMock; + + /** + * @var MarkUserNotified + */ + private $action; + + public function setUp() + { + $this->storageMock = $this->getMockBuilder(StorageInterface::class) + ->setMethods(['getId']) + ->getMockForAbstractClass(); + $this->authMock = $this->getMockBuilder(Auth::class) + ->disableOriginalConstructor() + ->getMock(); + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $contextMock->expects($this->once()) + ->method('getAuth') + ->willReturn($this->authMock); + $this->productMetadataMock = $this->getMockBuilder(ProductMetadataInterface::class) + ->getMockForAbstractClass(); + $this->notificationLoggerMock = $this->getMockBuilder(NotificationLogger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(LoggerInterface::class) + ->getMock(); + $resultFactoryMock = $this->getMockBuilder(ResultFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + $resultFactoryMock->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_JSON) + ->willReturn($this->resultMock); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->action = $objectManagerHelper->getObject( + MarkUserNotified::class, + [ + 'resultFactory' => $resultFactoryMock, + 'productMetadata' => $this->productMetadataMock, + 'notificationLogger' => $this->notificationLoggerMock, + 'context' => $contextMock, + 'logger' => $this->loggerMock + ] + ); + } + + public function testExecuteSuccess() + { + $this->authMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->storageMock); + $this->storageMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->productMetadataMock->expects($this->once()) + ->method('getVersion') + ->willReturn('999.999.999-alpha'); + $this->notificationLoggerMock->expects($this->once()) + ->method('log') + ->with(1, '999.999.999-alpha') + ->willReturn(true); + $this->resultMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'success' => true, + 'error_message' => '' + ], + false, + [] + )->willReturnSelf(); + $this->assertEquals($this->resultMock, $this->action->execute()); + } + + public function testExecuteFailedWithLocalizedException() + { + $this->authMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->storageMock); + $this->storageMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->productMetadataMock->expects($this->once()) + ->method('getVersion') + ->willReturn('999.999.999-alpha'); + $this->notificationLoggerMock->expects($this->once()) + ->method('log') + ->willThrowException(new LocalizedException(__('Error message'))); + $this->resultMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'success' => false, + 'error_message' => 'Error message' + ], + false, + [] + )->willReturnSelf(); + $this->assertEquals($this->resultMock, $this->action->execute()); + } + + public function testExecuteFailedWithException() + { + $this->authMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->storageMock); + $this->storageMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->productMetadataMock->expects($this->once()) + ->method('getVersion') + ->willReturn('999.999.999-alpha'); + $this->notificationLoggerMock->expects($this->once()) + ->method('log') + ->willThrowException(new \Exception('Any message')); + $this->resultMock->expects($this->once()) + ->method('setData') + ->with( + [ + 'success' => false, + 'error_message' => __('It is impossible to log user action') + ], + false, + [] + )->willReturnSelf(); + $this->assertEquals($this->resultMock, $this->action->execute()); + } +} diff --git a/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php b/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3ec00697507c1e2e8c85b7c9b633223d2c166b08 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Test/Unit/Model/Condition/CanViewNotificationTest.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Test\Unit\Model\Condition; + +use Magento\ReleaseNotification\Model\Condition\CanViewNotification; +use Magento\ReleaseNotification\Model\ResourceModel\Viewer\Logger; +use Magento\ReleaseNotification\Model\Viewer\Log; +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Backend\Model\Auth\Session; +use Magento\Framework\App\CacheInterface; + +/** + * Class CanViewNotificationTest + */ +class CanViewNotificationTest extends \PHPUnit\Framework\TestCase +{ + /** @var CanViewNotification */ + private $canViewNotification; + + /** @var Logger|\PHPUnit_Framework_MockObject_MockObject */ + private $viewerLoggerMock; + + /** @var ProductMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $productMetadataMock; + + /** @var Session|\PHPUnit_Framework_MockObject_MockObject */ + private $sessionMock; + + /** @var Log|\PHPUnit_Framework_MockObject_MockObject */ + private $logMock; + + /** @var $cacheStorageMock \PHPUnit_Framework_MockObject_MockObject|CacheInterface */ + private $cacheStorageMock; + + public function setUp() + { + $this->cacheStorageMock = $this->getMockBuilder(CacheInterface::class) + ->getMockForAbstractClass(); + $this->logMock = $this->getMockBuilder(Log::class) + ->getMock(); + $this->sessionMock = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->setMethods(['getUser', 'getId']) + ->getMock(); + $this->viewerLoggerMock = $this->getMockBuilder(Logger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productMetadataMock = $this->getMockBuilder(ProductMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new ObjectManager($this); + $this->canViewNotification = $objectManager->getObject( + CanViewNotification::class, + [ + 'viewerLogger' => $this->viewerLoggerMock, + 'session' => $this->sessionMock, + 'productMetadata' => $this->productMetadataMock, + 'cacheStorage' => $this->cacheStorageMock, + ] + ); + } + + public function testIsVisibleLoadDataFromCache() + { + $this->sessionMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->sessionMock); + $this->sessionMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->cacheStorageMock->expects($this->once()) + ->method('load') + ->with('release-notification-popup-1') + ->willReturn("0"); + $this->assertEquals(false, $this->canViewNotification->isVisible([])); + } + + /** + * @param bool $expected + * @param string $version + * @param string|null $lastViewVersion + * @dataProvider isVisibleProvider + */ + public function testIsVisible($expected, $version, $lastViewVersion) + { + $this->cacheStorageMock->expects($this->once()) + ->method('load') + ->with('release-notification-popup-1') + ->willReturn(false); + $this->sessionMock->expects($this->once()) + ->method('getUser') + ->willReturn($this->sessionMock); + $this->sessionMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $this->productMetadataMock->expects($this->once()) + ->method('getVersion') + ->willReturn($version); + $this->logMock->expects($this->once()) + ->method('getLastViewVersion') + ->willReturn($lastViewVersion); + $this->viewerLoggerMock->expects($this->once()) + ->method('get') + ->with(1) + ->willReturn($this->logMock); + $this->cacheStorageMock->expects($this->once()) + ->method('save') + ->with(false, 'release-notification-popup-1'); + $this->assertEquals($expected, $this->canViewNotification->isVisible([])); + } + + public function isVisibleProvider() + { + return [ + [false, '2.2.1-dev', '999.999.999-alpha'], + [true, '2.2.1-dev', '2.0.0'], + [true, '2.2.1-dev', null], + [false, '2.2.1-dev', '2.2.1'], + [true, '2.2.1-dev', '2.2.0'], + [true, '2.3.0', '2.2.0'], + [false, '2.2.2', '2.2.2'], + ]; + } +} diff --git a/app/code/Magento/ReleaseNotification/Ui/DataProvider/DataProvider.php b/app/code/Magento/ReleaseNotification/Ui/DataProvider/DataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..48f01b3b058e21bd189b656364e33382b81a2ab5 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/Ui/DataProvider/DataProvider.php @@ -0,0 +1,198 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Ui\DataProvider; + +use Magento\Framework\Api\Search\SearchCriteriaInterface; +use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\Data\Collection; +use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; + +/** + * Data Provider for the Release Notification UI component. + */ +class DataProvider implements DataProviderInterface +{ + /** + * Search result object. + * + * @var SearchResultInterface + */ + private $searchResult; + + /** + * Search criteria object. + * + * @var SearchCriteriaInterface + */ + private $searchCriteria; + + /** + * Data collection. + * + * @var Collection + */ + private $collection; + + /** + * Own name of this provider. + * + * @var string + */ + private $name; + + /** + * Provider configuration data. + * + * @var array + */ + private $data; + + /** + * @param string $name + * @param SearchResultInterface $searchResult + * @param SearchCriteriaInterface $searchCriteria + * @param Collection $collection + * @param array $data + */ + public function __construct( + $name, + SearchResultInterface $searchResult, + SearchCriteriaInterface $searchCriteria, + Collection $collection, + array $data = [] + ) { + $this->name = $name; + $this->searchResult = $searchResult; + $this->searchCriteria = $searchCriteria; + $this->collection = $collection; + $this->data = $data; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getConfigData() + { + return isset($this->data['config']) ? $this->data['config'] : []; + } + + /** + * {@inheritdoc} + */ + public function setConfigData($config) + { + $this->data['config'] = $config; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getMeta() + { + return []; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getFieldMetaInfo($fieldSetName, $fieldName) + { + return []; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getFieldSetMetaInfo($fieldSetName) + { + return []; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getFieldsMetaInfo($fieldSetName) + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getPrimaryFieldName() + { + return 'release_notification'; + } + + /** + * {@inheritdoc} + */ + public function getRequestFieldName() + { + return 'release_notification'; + } + + /** + * {@inheritdoc} + */ + public function getData() + { + return $this->collection->toArray(); + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function addFilter(\Magento\Framework\Api\Filter $filter) + { + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function addOrder($field, $direction) + { + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setLimit($offset, $size) + { + } + + /** + * {@inheritdoc} + */ + public function getSearchCriteria() + { + return $this->searchCriteria; + } + + /** + * {@inheritdoc} + */ + public function getSearchResult() + { + return $this->searchResult; + } +} diff --git a/app/code/Magento/ReleaseNotification/composer.json b/app/code/Magento/ReleaseNotification/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..b338420a2c143937ae563e0ef95918f9bcf31856 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-release-notification", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/module-user": "101.0.*", + "magento/module-backend": "100.2.*", + "magento/framework": "101.0.*" + }, + "type": "magento2-module", + "version": "100.2.0", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\ReleaseNotification\\": "" + } + } +} diff --git a/app/code/Magento/ReleaseNotification/etc/adminhtml/routes.xml b/app/code/Magento/ReleaseNotification/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b1ddc69ce3bdb9ca56127ee2f63af94433709b9 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> + <router id="admin"> + <route id="releaseNotification" frontName="releaseNotification"> + <module name="Magento_ReleaseNotification" /> + </route> + </router> +</config> diff --git a/app/code/Magento/ReleaseNotification/etc/module.xml b/app/code/Magento/ReleaseNotification/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..134d82e4f5776fb5547f437bcf41c202ccbdb10f --- /dev/null +++ b/app/code/Magento/ReleaseNotification/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_ReleaseNotification" setup_version="2.2.0"> + <sequence> + <module name="Magento_User"/> + <module name="Magento_Backend"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/ReleaseNotification/i18n/en_US.csv b/app/code/Magento/ReleaseNotification/i18n/en_US.csv new file mode 100644 index 0000000000000000000000000000000000000000..3fd0be63b4ab1924fc2e162379bbab7d0822354a --- /dev/null +++ b/app/code/Magento/ReleaseNotification/i18n/en_US.csv @@ -0,0 +1,107 @@ +"Next >","Next >" +"< Back","< Back" +"Done","Done" +"What's new for Magento 2.2.2","What's new for Magento 2.2.2" +"<p>Magento 2.2.2 offers an exciting set of features and enhancements, including:</p> + <br /> + <div class=""analytics-highlight""> + <h3>Advanced Reporting</h3> + <p>Gain valuable insights through a dynamic suite of product, order, and customer reports, + powered by Magento Business Intelligence.</p> + </div> + <div class=""developer-experience-highlight""> + <h3>Developer Experience</h3> + <p>We've improved the entire development lifecycle - installation, development, and maintenance + - while ensuring Magento's commitment to quality.</p> + </div> + <div class=""b2b-highlight""> + <h3>Business-to-Business (B2B) <span>Magento Commerce only</span></h3> + <p>Features to manage your complex company accounts, rapid reordering, new buyers' roles and + permissions, and more.</p> + </div> + <p>Release notes and additional details can be found at + <a href=""http://devdocs.magento.com/"" target=""_blank"">Magento DevDocs</a>. + </p>","<p>Magento 2.2.2 offers an exciting set of features and enhancements, including:</p> + <br /> + <div class=""analytics-highlight""> + <h3>Advanced Reporting</h3> + <p>Gain valuable insights through a dynamic suite of product, order, and customer reports, + powered by Magento Business Intelligence.</p> + </div> + <div class=""developer-experience-highlight""> + <h3>Developer Experience</h3> + <p>We've improved the entire development lifecycle - installation, development, and maintenance + - while ensuring Magento's commitment to quality.</p> + </div> + <div class=""b2b-highlight""> + <h3>Business-to-Business (B2B) <span>Magento Commerce only</span></h3> + <p>Features to manage your complex company accounts, rapid reordering, new buyers' roles and + permissions, and more.</p> + </div> + <p>Release notes and additional details can be found at + <a href=""http://devdocs.magento.com/"" target=""_blank"">Magento DevDocs</a>. + </p>" +"Advanced Reporting","Advanced Reporting" +"<p>Advanced Reporting + provides you with a dynamic suite of reports with rich insights about the health of your + business.</p><br /><p>As part of the Advanced Reporting service, we may also use your customer + data for such purposes as benchmarking, improving our products and services, and providing you + with new and improved analytics.</p><br /><p>By using Magento 2.2, you agree to the Advanced + Reporting <a href=""https://magento.com/legal/terms/privacy"" target=""_blank"">Privacy Policy</a> and + <a href=""https://magento.com/legal/terms/cloud-terms"" target=""_blank"">Terms + of Service</a>. You may opt out at any time from the Stores Configuration page.</p> + ","<p>Advanced Reporting + provides you with a dynamic suite of reports with rich insights about the health of your + business.</p><br /><p>As part of the Advanced Reporting service, we may also use your customer + data for such purposes as benchmarking, improving our products and services, and providing you + with new and improved analytics.</p><br /><p>By using Magento 2.2, you agree to the Advanced + Reporting <a href=""https://magento.com/legal/terms/privacy"" target=""_blank"">Privacy Policy</a> and + <a href=""https://magento.com/legal/terms/cloud-terms"" target=""_blank"">Terms + of Service</a>. You may opt out at any time from the Stores Configuration page.</p> + " +"Developer Experience","Developer Experience" +"<p>Magento's 2.2.2 release offers a set of improvements that were developed using increased + quality standards. The release includes these features, among others: + </p> + <ul> + <li><span>GitHub Community Moderator Team</span></li> + <li><span>GitHub Community Videos</span></li> + <li><span>DevDocs Enhancements</span></li> + </ul> + <p>Find the 2.2.2 details and future plans in the + <a href=""http://community.magento.com/devblog"" target=""_blank"">Magento DevBlog</a>. + </p>","<p>Magento's 2.2.2 release offers a set of improvements that were developed using increased + quality standards. The release includes these features, among others: + </p> + <ul> + <li><span>GitHub Community Moderator Team</span></li> + <li><span>GitHub Community Videos</span></li> + <li><span>DevDocs Enhancements</span></li> + </ul> + <p>Find the 2.2.2 details and future plans in the + <a href=""http://community.magento.com/devblog"" target=""_blank"">Magento DevBlog</a>. + </p>" +"Business-to-Business (B2B) <span>Magento Commerce only</span>","Business-to-Business (B2B) <span>Magento Commerce only</span>" +"<p>Magento Commerce 2.2.2 offers rich new functionality that empowers B2B merchants to transform + their online purchasing experience to achieve greater operational efficiency, improved customer + service, and sales growth. New capabilities include: + </p> + <ul> + <li><span>Company accounts with multiple tiers of buyers</span></li> + <li><span>Buyer roles and permissions</span></li> + <li><span>Custom catalogs and pricing</span></li> + <li><span>Quoting support</span></li> + <li><span>Rapid reorder experience</span></li> + <li><span>Payments on credit</span></li> + </ul>","<p>Magento Commerce 2.2.2 offers rich new functionality that empowers B2B merchants to transform + their online purchasing experience to achieve greater operational efficiency, improved customer + service, and sales growth. New capabilities include: + </p> + <ul> + <li><span>Company accounts with multiple tiers of buyers</span></li> + <li><span>Buyer roles and permissions</span></li> + <li><span>Custom catalogs and pricing</span></li> + <li><span>Quoting support</span></li> + <li><span>Rapid reorder experience</span></li> + <li><span>Payments on credit</span></li> + </ul>" diff --git a/app/code/Magento/ReleaseNotification/registration.php b/app/code/Magento/ReleaseNotification/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..c5bce27f203879d6ee86adee1917c0e7603653fc --- /dev/null +++ b/app/code/Magento/ReleaseNotification/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_ReleaseNotification', + __DIR__ +); diff --git a/app/code/Magento/ReleaseNotification/view/adminhtml/layout/adminhtml_dashboard_index.xml b/app/code/Magento/ReleaseNotification/view/adminhtml/layout/adminhtml_dashboard_index.xml new file mode 100644 index 0000000000000000000000000000000000000000..34ce4e3daff84e7c5637c0199f0958de8a00d60d --- /dev/null +++ b/app/code/Magento/ReleaseNotification/view/adminhtml/layout/adminhtml_dashboard_index.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="content"> + <uiComponent name="release_notification"> + <visibilityCondition name="can_view_notification" className="Magento\ReleaseNotification\Model\Condition\CanViewNotification"/> + </uiComponent> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml new file mode 100644 index 0000000000000000000000000000000000000000..708d366f455bfe93c6d526988124e59f661f299b --- /dev/null +++ b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml @@ -0,0 +1,375 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="provider" xsi:type="string">release_notification.release_notification_data_source</item> + </item> + <item name="label" xsi:type="string" translate="true">Release Notification</item> + <item name="template" xsi:type="string">templates/form/collapsible</item> + </argument> + <settings> + <namespace>release_notification</namespace> + <dataScope>data</dataScope> + <deps> + <dep>release_notification.release_notification_data_source</dep> + </deps> + </settings> + <dataSource name="release_notification_data_source"> + <argument name="dataProvider" xsi:type="configurableObject"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="data" xsi:type="array"> + <item name="logAction" xsi:type="url" path="releaseNotification/notification/markUserNotified"/> + </item> + </item> + </argument> + </argument> + <argument name="data" xsi:type="array"> + <item name="js_config" xsi:type="array"> + <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item> + </item> + </argument> + <dataProvider class="Magento\ReleaseNotification\Ui\DataProvider\DataProvider" name="release_notification_data_source"/> + </dataSource> + <modal name="release_notification_modal" component="Magento_ReleaseNotification/js/modal/component"> + <settings> + <onCancel>actionCancel</onCancel> + <state>true</state> + <options> + <option name="modalClass" xsi:type="string">release-notification-modal</option> + <option name="title" xsi:type="string" translate="true">What's new for Magento 2.2.2</option> + <option name="type" xsi:type="string">popup</option> + <option name="responsive" xsi:type="boolean">true</option> + <option name="innerScroll" xsi:type="boolean">true</option> + <option name="autoOpen" xsi:type="boolean">true</option> + </options> + </settings> + <fieldset name="release_notification_fieldset"> + <settings> + <label/> + </settings> + <container name="release_notification_text" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="additionalClasses" xsi:type="string">release-notification-text</item> + <item name="text" xsi:type="string" translate="true"><![CDATA[ + <p>Magento 2.2.2 offers an exciting set of features and enhancements, including:</p> + <br /> + <div class="analytics-highlight"> + <h3>Advanced Reporting</h3> + <p>Gain valuable insights through a dynamic suite of product, order, and customer reports, + powered by Magento Business Intelligence.</p> + </div> + <div class="developer-experience-highlight"> + <h3>Developer Experience</h3> + <p>We've improved the entire development lifecycle - installation, development, and maintenance + - while ensuring Magento's commitment to quality.</p> + </div> + <div class="b2b-highlight"> + <h3>Business-to-Business (B2B) <span>Magento Commerce only</span></h3> + <p>Features to manage your complex company accounts, rapid reordering, new buyers' roles and + permissions, and more.</p> + </div> + <p>Release notes and additional details can be found at + <a href="http://devdocs.magento.com/" target="_blank">Magento DevDocs</a>. + </p>]]> + </item> + </item> + </argument> + </container> + <container name="release_notification_buttons"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + </item> + </argument> + <button name="release_notification_button_next" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-next</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = release_notification_modal</item> + <item name="actionName" xsi:type="string">closeModal</item> + </item> + <item name="1" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = analytics_modal</item> + <item name="actionName" xsi:type="string">openModal</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title><![CDATA[Next >]]></title> + </settings> + </button> + </container> + </fieldset> + </modal> + <modal name="analytics_modal" component="Magento_ReleaseNotification/js/modal/component"> + <settings> + <onCancel>actionCancel</onCancel> + <options> + <option name="modalClass" xsi:type="string">analytics-subscription-modal</option> + <option name="title" xsi:type="string" translate="true">Advanced Reporting</option> + <option name="type" xsi:type="string">popup</option> + <option name="responsive" xsi:type="boolean">true</option> + <option name="innerScroll" xsi:type="boolean">true</option> + <option name="autoOpen" xsi:type="boolean">false</option> + </options> + </settings> + <fieldset name="release_notification_fieldset"> + <settings> + <label/> + </settings> + <container name="release_notification_text" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="additionalClasses" xsi:type="string">release-notification-text</item> + <item name="text" xsi:type="string" translate="true"><![CDATA[<p>Advanced Reporting + provides you with a dynamic suite of reports with rich insights about the health of your + business.</p><br /><p>As part of the Advanced Reporting service, we may also use your customer + data for such purposes as benchmarking, improving our products and services, and providing you + with new and improved analytics.</p><br /><p>By using Magento 2.2, you agree to the Advanced + Reporting <a href="https://magento.com/legal/terms/privacy" target="_blank">Privacy Policy</a> + and <a href="https://magento.com/legal/terms/cloud-terms" target="_blank">Terms + of Service</a>. You may opt out at any time from the Stores Configuration page.</p>]]> + </item> + </item> + </argument> + </container> + <container name="release_notification_buttons"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + </item> + </argument> + <button name="release_notification_button_back" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-back</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = analytics_modal</item> + <item name="actionName" xsi:type="string">closeModal</item> + </item> + <item name="1" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = release_notification_modal</item> + <item name="actionName" xsi:type="string">openModal</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title><![CDATA[< Back]]></title> + </settings> + </button> + <button name="release_notification_button_next" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-next</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = analytics_modal</item> + <item name="actionName" xsi:type="string">closeModal</item> + </item> + <item name="1" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = developer_experience_modal</item> + <item name="actionName" xsi:type="string">openModal</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title><![CDATA[Next >]]></title> + </settings> + </button> + </container> + </fieldset> + </modal> + <modal name="developer_experience_modal" component="Magento_ReleaseNotification/js/modal/component"> + <settings> + <onCancel>actionCancel</onCancel> + <options> + <option name="modalClass" xsi:type="string">developer-experience-modal</option> + <option name="title" xsi:type="string" translate="true">Developer Experience</option> + <option name="type" xsi:type="string">popup</option> + <option name="responsive" xsi:type="boolean">true</option> + <option name="innerScroll" xsi:type="boolean">true</option> + <option name="autoOpen" xsi:type="boolean">false</option> + </options> + </settings> + <fieldset name="release_notification_fieldset"> + <settings> + <label/> + </settings> + <container name="release_notification_text" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="additionalClasses" xsi:type="string">release-notification-text</item> + <item name="text" xsi:type="string" translate="true"><![CDATA[ + <p>Magento's 2.2.2 release offers a set of improvements that were developed using increased + quality standards. The release includes these features, among others: + </p> + <ul> + <li><span>GitHub Community Moderator Team</span></li> + <li><span>GitHub Community Videos</span></li> + <li><span>DevDocs Enhancements</span></li> + </ul> + <p>Find the 2.2.2 details and future plans in the + <a href="http://community.magento.com/devblog" target="_blank">Magento DevBlog</a>. + </p>]]> + </item> + </item> + </argument> + </container> + <container name="release_notification_buttons"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + </item> + </argument> + <button name="release_notification_button_back" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-back</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = developer_experience_modal</item> + <item name="actionName" xsi:type="string">closeModal</item> + </item> + <item name="1" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = analytics_modal</item> + <item name="actionName" xsi:type="string">openModal</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title><![CDATA[< Back]]></title> + </settings> + </button> + <button name="release_notification_button_next" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-next</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = developer_experience_modal</item> + <item name="actionName" xsi:type="string">closeModal</item> + </item> + <item name="1" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = b2b_modal</item> + <item name="actionName" xsi:type="string">openModal</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title><![CDATA[Next >]]></title> + </settings> + </button> + </container> + </fieldset> + </modal> + <modal name="b2b_modal" component="Magento_ReleaseNotification/js/modal/component"> + <settings> + <onCancel>actionCancel</onCancel> + <options> + <option name="modalClass" xsi:type="string">b2b-modal</option> + <option name="title" xsi:type="string" translate="true"><![CDATA[Business-to-Business (B2B) + <span>Magento Commerce only</span>]]></option> + <option name="type" xsi:type="string">popup</option> + <option name="responsive" xsi:type="boolean">true</option> + <option name="innerScroll" xsi:type="boolean">true</option> + <option name="autoOpen" xsi:type="boolean">false</option> + </options> + </settings> + <fieldset name="release_notification_fieldset"> + <settings> + <label/> + </settings> + <container name="release_notification_text" template="ui/form/components/complex"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + <item name="additionalClasses" xsi:type="string">release-notification-text</item> + <item name="text" xsi:type="string" translate="true"><![CDATA[ + <p>Magento Commerce 2.2.2 offers rich new functionality that empowers B2B merchants to transform + their online purchasing experience to achieve greater operational efficiency, improved customer + service, and sales growth. New capabilities include: + </p> + <ul> + <li><span>Company accounts with multiple tiers of buyers</span></li> + <li><span>Buyer roles and permissions</span></li> + <li><span>Custom catalogs and pricing</span></li> + <li><span>Quoting support</span></li> + <li><span>Rapid reorder experience</span></li> + <li><span>Payments on credit</span></li> + </ul>]]> + </item> + </item> + </argument> + </container> + <container name="release_notification_buttons"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="label" xsi:type="string"/> + </item> + </argument> + <button name="release_notification_button_back" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-back</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = b2b_modal</item> + <item name="actionName" xsi:type="string">closeModal</item> + </item> + <item name="1" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = developer_experience_modal</item> + <item name="actionName" xsi:type="string">openModal</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title><![CDATA[< Back]]></title> + </settings> + </button> + <button name="release_notification_button_next" displayArea="actions-secondary"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="buttonClasses" xsi:type="string">release-notification-button-next</item> + <item name="actions" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="targetName" xsi:type="string">ns = ${ $.ns }, index = b2b_modal</item> + <item name="actionName" xsi:type="string">closeReleaseNotes</item> + </item> + </item> + </item> + </argument> + <settings> + <displayAsLink>true</displayAsLink> + <title>Done</title> + </settings> + </button> + </container> + </fieldset> + </modal> +</form> diff --git a/app/code/Magento/ReleaseNotification/view/adminhtml/web/js/modal/component.js b/app/code/Magento/ReleaseNotification/view/adminhtml/web/js/modal/component.js new file mode 100644 index 0000000000000000000000000000000000000000..b74ef2af1a04d14c599aa6d33c6885448a10c183 --- /dev/null +++ b/app/code/Magento/ReleaseNotification/view/adminhtml/web/js/modal/component.js @@ -0,0 +1,65 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Ui/js/modal/modal-component', + 'Magento_Ui/js/modal/alert', + 'mage/translate' +], function ($, Modal, alert, $t) { + 'use strict'; + + return Modal.extend({ + defaults: { + imports: { + logAction: '${ $.provider }:data.logAction' + } + }, + + /** + * Error handler. + * + * @param {Object} xhr - request result. + */ + onError: function (xhr) { + if (xhr.statusText === 'abort') { + return; + } + + alert({ + content: xhr.message || $t('An error occurred while logging process.') + }); + }, + + /** + * Log release notes show + */ + logReleaseNotesShow: function () { + var self = this, + data = { + 'form_key': window.FORM_KEY + }; + + $.ajax({ + type: 'POST', + url: this.logAction, + data: data, + showLoader: true + }).done(function (xhr) { + if (xhr.error) { + self.onError(xhr); + } + }).fail(this.onError); + }, + + /** + * Close release notes + */ + closeReleaseNotes: function () { + this.logReleaseNotesShow(); + this.closeModal(); + } + }); +}); diff --git a/app/code/Magento/ReviewAnalytics/LICENSE.txt b/app/code/Magento/ReviewAnalytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/ReviewAnalytics/LICENSE_AFL.txt b/app/code/Magento/ReviewAnalytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/ReviewAnalytics/README.md b/app/code/Magento/ReviewAnalytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b078083dfb7dc7eb079051fb7e3d0de68812ad1e --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/README.md @@ -0,0 +1,3 @@ +# Magento_ReviewAnalytics module + +The Magento_ReviewAnalytics module configures data definitions for a data collection related to the Review module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/ReviewAnalytics/composer.json b/app/code/Magento/ReviewAnalytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..b31c420e181bfa550f32880892c3d619ca52011b --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-review-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-review": "100.2.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\ReviewAnalytics\\": "" + } + } +} diff --git a/app/code/Magento/ReviewAnalytics/etc/analytics.xml b/app/code/Magento/ReviewAnalytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..cd5d1b2c1af4c66db966c67a435b04ec548f6feb --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/etc/analytics.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="reviews"> + <providers> + <reportProvider name="reviews" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>reviews</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="rating_option_votes"> + <providers> + <reportProvider name="rating_option_votes" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>rating_option_votes</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/ReviewAnalytics/etc/module.xml b/app/code/Magento/ReviewAnalytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..65df87bac4af132a9c06033234ba7f0fa2000423 --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_ReviewAnalytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Review"/> + <module name="Magento_Analytics"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/ReviewAnalytics/etc/reports.xml b/app/code/Magento/ReviewAnalytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..8dd508983aced8aac7b3db3d94e6947e627101a3 --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/etc/reports.xml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="reviews" connection="default"> + <source name="review"> + <attribute name="review_id"/> + <attribute name="created_at"/> + <attribute name="entity_pk_value"/> + </source> + </report> + <report name="rating_option_votes" connection="default"> + <source name="rating_option_vote_aggregated"> + <attribute name="primary_id"/> + <attribute name="entity_pk_value"/> + <attribute name="store_id"/> + <attribute name="rating_id"/> + <attribute name="percent_approved"/> + </source> + </report> +</config> diff --git a/app/code/Magento/ReviewAnalytics/registration.php b/app/code/Magento/ReviewAnalytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..6b795ca04c61b8fe81b5fb21b2e2ba7deb7ab7e8 --- /dev/null +++ b/app/code/Magento/ReviewAnalytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_ReviewAnalytics', + __DIR__ +); diff --git a/app/code/Magento/SalesAnalytics/LICENSE.txt b/app/code/Magento/SalesAnalytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/SalesAnalytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/SalesAnalytics/LICENSE_AFL.txt b/app/code/Magento/SalesAnalytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/SalesAnalytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/SalesAnalytics/README.md b/app/code/Magento/SalesAnalytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..70f456c97d4b32e41f73eefc555aed7bb9eb82fa --- /dev/null +++ b/app/code/Magento/SalesAnalytics/README.md @@ -0,0 +1,3 @@ +# Magento_SalesAnalytics module + +The Magento_SalesAnalytics module configures data definitions for a data collection related to the Sales module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/SalesAnalytics/composer.json b/app/code/Magento/SalesAnalytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..7c9270a503b0d0aaf824c879ff6396fe0ad906d6 --- /dev/null +++ b/app/code/Magento/SalesAnalytics/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-sales-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-sales": "100.2.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\SalesAnalytics\\": "" + } + } +} diff --git a/app/code/Magento/SalesAnalytics/etc/analytics.xml b/app/code/Magento/SalesAnalytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..be6c4dfde9b192929bc4aca70bf22a4770f0918f --- /dev/null +++ b/app/code/Magento/SalesAnalytics/etc/analytics.xml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="orders"> + <providers> + <reportProvider class="Magento\Analytics\ReportXml\ReportProvider" name="orders"> + <parameters> + <name>orders</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="order_items"> + <providers> + <reportProvider class="Magento\Analytics\ReportXml\ReportProvider" name="order_items"> + <parameters> + <name>order_items</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="order_addresses"> + <providers> + <reportProvider class="Magento\Analytics\ReportXml\ReportProvider" name="order_addresses"> + <parameters> + <name>order_addresses</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/SalesAnalytics/etc/module.xml b/app/code/Magento/SalesAnalytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..7a15075a4bc21b6e201d0250419388dd13a55089 --- /dev/null +++ b/app/code/Magento/SalesAnalytics/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_SalesAnalytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Sales"/> + <module name="Magento_Analytics"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/SalesAnalytics/etc/reports.xml b/app/code/Magento/SalesAnalytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..bb6bdb800e9bf80576fb7918be8b73447b0ed78d --- /dev/null +++ b/app/code/Magento/SalesAnalytics/etc/reports.xml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="orders" connection="sales"> + <source name="sales_order"> + <attribute name="entity_id"/> + <attribute name="created_at"/> + <attribute name="customer_id"/> + <attribute name="status"/> + <attribute name="base_grand_total"/> + <attribute name="base_tax_amount"/> + <attribute name="base_shipping_amount"/> + <attribute name="coupon_code" function="sha1"/> + <attribute name="store_id"/> + <attribute name="store_name"/> + <attribute name="base_discount_amount"/> + <attribute name="base_subtotal"/> + <attribute name="base_total_refunded"/> + <attribute name="shipping_method"/> + <attribute name="shipping_address_id"/> + <attribute name="customer_email" function="sha1"/> + <attribute name="base_total_online_refunded"/> + <attribute name="base_total_offline_refunded"/> + <attribute name="base_currency_code"/> + <attribute name="billing_address_id"/> + </source> + </report> + <report name="order_items" connection="sales"> + <source name="sales_order_item"> + <attribute name="item_id"/> + <attribute name="created_at"/> + <attribute name="name"/> + <attribute name="base_price"/> + <attribute name="qty_ordered"/> + <attribute name="order_id"/> + <attribute name="sku"/> + <attribute name="product_id"/> + <attribute name="store_id"/> + </source> + </report> + <report name="order_addresses" connection="sales"> + <source name="sales_order_address"> + <attribute name="entity_id"/> + <attribute name="customer_id"/> + <attribute name="city"/> + <attribute name="region"/> + <attribute name="country_id"/> + </source> + </report> +</config> diff --git a/app/code/Magento/SalesAnalytics/registration.php b/app/code/Magento/SalesAnalytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..eff2c5b1a2c05b77ce82c3197c53476a6bb383e5 --- /dev/null +++ b/app/code/Magento/SalesAnalytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_SalesAnalytics', + __DIR__ +); diff --git a/app/code/Magento/WishlistAnalytics/LICENSE.txt b/app/code/Magento/WishlistAnalytics/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49525fd99da9c51e6d85420266d41cb3d6b7a648 --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/WishlistAnalytics/LICENSE_AFL.txt b/app/code/Magento/WishlistAnalytics/LICENSE_AFL.txt new file mode 100644 index 0000000000000000000000000000000000000000..f39d641b18a19e56df6c8a3e4038c940fb886b32 --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/WishlistAnalytics/README.md b/app/code/Magento/WishlistAnalytics/README.md new file mode 100644 index 0000000000000000000000000000000000000000..999fc835626da52de6f48c9f639e6b6ef41d551a --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/README.md @@ -0,0 +1,3 @@ +# Magento_WishlistAnalytics module + +The Magento_WishlistAnalytics module configures data definitions for a data collection related to the Wishlist module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/WishlistAnalytics/composer.json b/app/code/Magento/WishlistAnalytics/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..20f414c00c320f4e5709f1ea1f490741a7c17012 --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/composer.json @@ -0,0 +1,23 @@ +{ + "name": "magento/module-wishlist-analytics", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-wishlist": "100.2.*" + }, + "type": "magento2-module", + "version": "100.2.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\WishlistAnalytics\\": "" + } + } +} diff --git a/app/code/Magento/WishlistAnalytics/etc/analytics.xml b/app/code/Magento/WishlistAnalytics/etc/analytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..0b2531fe0df67ee9bc9a5268cd566144a867d394 --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/etc/analytics.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/analytics.xsd"> + <file name="wishlists"> + <providers> + <reportProvider name="wishlists" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>wishlists</name> + </parameters> + </reportProvider> + </providers> + </file> + <file name="wishlist_items"> + <providers> + <reportProvider name="wishlist_items" class="Magento\Analytics\ReportXml\ReportProvider"> + <parameters> + <name>wishlist_items</name> + </parameters> + </reportProvider> + </providers> + </file> +</config> diff --git a/app/code/Magento/WishlistAnalytics/etc/module.xml b/app/code/Magento/WishlistAnalytics/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..159ed86ee171a156975b1dccb56101d77720b23a --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_WishlistAnalytics" setup_version="2.0.0"> + <sequence> + <module name="Magento_Wishlist"/> + <module name="Magento_Analytics"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/WishlistAnalytics/etc/reports.xml b/app/code/Magento/WishlistAnalytics/etc/reports.xml new file mode 100644 index 0000000000000000000000000000000000000000..0125fa93f815a6cd4f76ae65cda5f4aed4851215 --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/etc/reports.xml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Analytics:etc/reports.xsd"> + <report name="wishlists" connection="default"> + <source name="wishlist"> + <attribute name="wishlist_id"/> + <attribute name="customer_id"/> + </source> + </report> + <report name="wishlist_items" connection="default"> + <source name="wishlist_item"> + <attribute name="wishlist_item_id"/> + <attribute name="added_at"/> + <attribute name="qty"/> + <attribute name="store_id"/> + <attribute name="wishlist_id"/> + <attribute name="product_id"/> + </source> + </report> +</config> diff --git a/app/code/Magento/WishlistAnalytics/registration.php b/app/code/Magento/WishlistAnalytics/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..eacf1e0d78bcbe83c90b23d882bb0e911642c3dc --- /dev/null +++ b/app/code/Magento/WishlistAnalytics/registration.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_WishlistAnalytics', + __DIR__ +); diff --git a/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less index db29bfcaf6355b38465ead3aaec53b2a907a38d8..45f1a835d18d8d7da58d629bf593edbab0d4bbeb 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less @@ -18,157 +18,108 @@ // _____________________________________________ .dashboard-advanced-reports { - .lib-vendor-prefix-display(flex); - border-color: @color-gray89; - border-style: solid; - border-width: 1px 0; - margin-bottom: @indent__l; - padding: @indent__m 0; + .lib-vendor-prefix-display(flex); + border-color: @color-gray89; + border-style: solid; + border-width: 1px 0; + margin-bottom: @indent__l; + padding: @indent__m 0; } .dashboard-advanced-reports-title { - &:extend(.dashboard-item-title all); - margin-bottom: @indent__s; + &:extend(.dashboard-item-title all); + margin-bottom: @indent__s; } .dashboard-advanced-reports-content { - line-height: @line-height__xl; + line-height: @line-height__xl; } .dashboard-advanced-reports-actions { - .lib-vendor-prefix-flex-basis(auto); - .lib-vendor-prefix-flex-grow(1); - .lib-vendor-prefix-flex-shrink(1); - align-self: center; - margin-left: @indent__m; - margin-right: @page-main-actions__padding; - text-align: right; + .lib-vendor-prefix-flex-basis(auto); + .lib-vendor-prefix-flex-grow(1); + .lib-vendor-prefix-flex-shrink(1); + align-self: center; + margin-left: @indent__m; + margin-right: @page-main-actions__padding; + text-align: right; } .action-advanced-reports { - &:extend(.abs-action-l all); - &:extend(.abs-action-pattern all); - background-color: @button-advanced-reports__background-color; - border-color: @button-advanced-reports__background-color; + &:extend(.abs-action-l all); + &:extend(.abs-action-pattern all); + background-color: @button-advanced-reports__background-color; + border-color: @button-advanced-reports__background-color; + color: @button-advanced-reports__color; + text-shadow: 1px 1px 0 rgba(0, 0, 0, .25); + white-space: nowrap; + + &:after { + &:extend(.abs-icon all); + content: @icon-external-link__content; + font-size: @font-size__xs; + vertical-align: super; + } + + &:hover, + &:active, + &:focus { + background-color: @button-advanced-reports__hover__background-color; + border-color: @button-advanced-reports__hover__border-color; + box-shadow: @button__hover__box-shadow; color: @button-advanced-reports__color; - text-shadow: 1px 1px 0 rgba(0, 0, 0, .25); - white-space: nowrap; - - &:after { - &:extend(.abs-icon all); - content: @icon-external-link__content; - font-size: @font-size__xs; - vertical-align: super; - } - - &:hover, - &:active, - &:focus { - background-color: @button-advanced-reports__hover__background-color; - border-color: @button-advanced-reports__hover__border-color; - box-shadow: @button__hover__box-shadow; - color: @button-advanced-reports__color; - text-decoration: none; - } - - &.disabled, - &[disabled] { - cursor: default; - opacity: @disabled__opacity; - pointer-events: none; - } + text-decoration: none; + } + + &.disabled, + &[disabled] { + cursor: default; + opacity: @disabled__opacity; + pointer-events: none; + } } // // Modal on dashboard // --------------------------------------------- -.advanced-reports-decline-subscription-modal, .advanced-reports-subscription-modal { - .modal-inner-wrap { - max-width: 80rem; - } -} + .modal-inner-wrap { + max-width: 75rem; + margin-top: 13rem; -.advanced-reports-subscription-modal { - .admin__fieldset { - padding: 0; - } -} + .modal-content, .modal-header { + padding-left: 4rem; + padding-right: 4rem; -.advanced-reports-decline-subscription-modal { - .advanced-reports-subscription-text { - margin-bottom: 0; + .action-close { + display: none; + } } + } - .modal-content { - padding-bottom: 0; - } + .admin__fieldset { + padding: 0; + } } .advanced-reports-subscription-text { - line-height: @line-height__xl; - - .list { - .lib-list-reset-styles(@_margin: @indent__m 0, @_padding: 0); - - li { - padding-left: @indent__l; - position: relative; - - &:before { - content: '\2022'; - left: 1rem; - position: absolute; - } - - + li { - margin-top: @indent__m; - } - } - } + line-height: @line-height__xl; + padding-bottom: 8rem; } -.advanced-reports-subscription-actions { - font-size: 0; - text-align: justify; - - &:after { - content: ''; - display: inline-block; - height: 0; - overflow: hidden; - visibility: hidden; - width: 100%; - } - - .action-basic, - .action-additional { - &:extend(.abs-action-l all); - } - - .advanced-reports-subscription-postpone, - .advanced-reports-subscription-disable, - .advanced-reports-subscription-enable { - display: inline-block; - vertical-align: top; - } +.advanced-reports-subscription-close { + display: inline-block; + vertical-align: top; + float: right; } -.advanced-reports-subscription-enable { - .admin__field { - max-width: 22rem; - } - - .admin__field-label { - font-size: @font-size__s; - line-height: @line-height__l; - } - - .action-basic { - &:extend(.abs-action-primary all); - margin-bottom: @indent__s; - } +.advanced-reports-subscription-modal { + h1:first-of-type { + background: url("Magento_Analytics::images/analytics-icon.svg") no-repeat; + background-size: 55px 49.08px; + padding: 1.5rem 0 2rem 7rem; + } } // @@ -190,3 +141,27 @@ .config-additional-comment-content { line-height: @line-height__l; } + +.config-vertical-title { + clear: both; + color: #303030; + font-size: 1.7rem; + font-weight: 600; + letter-spacing: .025em; + padding: 1.9rem 2.8rem 1.9rem 0; + position: relative; +} + +.config-vertical-comment { + line-height: 1.5; + margin-bottom: .5em; + margin-top: 1rem; +} + +#row_analytics_general_vertical { + >td.config-vertical-label { + >label.admin__field-label { + padding-right: 0; + } + } +} diff --git a/app/design/adminhtml/Magento/backend/Magento_Analytics/web/images/analytics-icon.svg b/app/design/adminhtml/Magento/backend/Magento_Analytics/web/images/analytics-icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..fde91d775d444924774ba1f2d3907c529863dd2d --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_Analytics/web/images/analytics-icon.svg @@ -0,0 +1,84 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="120px" + height="105px" viewBox="0 0 120 105" enable-background="new 0 0 120 105" xml:space="preserve"> +<g id="Layer_1" display="none"> + <rect x="2.904" y="27.946" display="inline" fill="#F06524" width="113.745" height="12.408"/> + <path display="inline" fill="#636667" d="M95.811,85.511c-0.753-2.551-1.775-5.013-3.059-7.344 + c-0.207-0.387-0.134-0.884,0.191-1.208c2.857-2.869,2.857-7.489,0.013-10.346l-3.649-3.686c-2.873-2.86-7.493-2.86-10.348-0.016 + c-0.334,0.336-0.828,0.409-1.228,0.193c-2.321-1.271-4.785-2.295-7.337-3.047c-0.419-0.126-0.714-0.525-0.712-0.979 + c0.004-1.947-0.766-3.812-2.139-5.187c-1.37-1.377-3.233-2.148-5.176-2.148h-5.184c-4.039,0-7.312,3.274-7.312,7.314 + c0.001,0.464-0.294,0.863-0.728,0.993c-2.537,0.747-5.002,1.771-7.335,3.051c-0.391,0.213-0.882,0.143-1.199-0.178 + c-1.377-1.381-3.236-2.15-5.176-2.15c-1.938,0-3.799,0.77-5.168,2.143l-3.688,3.744c-2.833,2.845-2.833,7.465,0.011,10.321 + c0.336,0.336,0.41,0.835,0.191,1.24c-1.269,2.312-2.294,4.772-3.045,7.324c-0.487,1.657,0.46,3.396,2.117,3.885 + c1.658,0.487,3.396-0.46,3.884-2.117c0.624-2.12,1.475-4.164,2.538-6.101c1.544-2.863,1.027-6.375-1.262-8.653 + c-0.405-0.407-0.405-1.082,0.009-1.497l3.686-3.742c0.181-0.182,0.449-0.291,0.727-0.291c0.279,0,0.545,0.109,0.743,0.307 + c2.271,2.289,5.794,2.805,8.634,1.253c1.944-1.065,3.993-1.918,6.115-2.541c3.105-0.933,5.213-3.781,5.2-7.005 + C56.126,58.473,56.6,58,57.184,58h5.184c0.281,0,0.55,0.112,0.75,0.31c0.199,0.2,0.309,0.471,0.309,0.752 + c-0.012,3.217,2.098,6.065,5.185,6.992c2.138,0.631,4.187,1.482,6.129,2.545c2.854,1.546,6.369,1.026,8.646-1.269 + c0.403-0.401,1.075-0.401,1.492,0.015l3.639,3.675c0.42,0.424,0.42,1.098,0.007,1.515c-2.283,2.265-2.802,5.783-1.261,8.628 + c1.071,1.952,1.922,3.997,2.545,6.116c0.489,1.657,2.228,2.604,3.886,2.116C95.351,88.907,96.297,87.169,95.811,85.511z"/> + <path display="inline" fill="#636667" d="M118.29,1.486C117.337,0.536,116.046,0,114.7,0H4.843 + C2.035,0.014-0.224,2.283-0.224,5.076v89.103c-0.006,1.334,0.524,2.629,1.475,3.584c0.951,0.955,2.244,1.492,3.592,1.492h109.851 + c1.35,0.005,2.645-0.529,3.6-1.484c0.955-0.954,1.49-2.254,1.482-3.605V5.076C119.776,3.73,119.243,2.439,118.29,1.486z + M113.521,6.255v18.564H6.032V6.255H113.521z M41.2,93c1.493-8.922,9.23-15.729,18.576-15.729c9.348,0,17.083,6.808,18.576,15.729 + H41.2z M84.673,93c-1.542-12.39-12.089-21.984-24.896-21.984C46.97,71.016,36.422,80.61,34.879,93H6.032V31.074h107.49V93H84.673z" + /> + <path display="inline" fill="#636667" d="M21.661,19.955c-1.204,1.203-2.836,1.879-4.539,1.879c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418s6.418,2.873,6.418,6.418C23.541,17.118,22.865,18.751,21.661,19.955z"/> + <circle display="inline" fill="#636667" cx="31.611" cy="15.416" r="6.418"/> + <path display="inline" fill="#636667" d="M52.518,15.416c-0.009,3.545-2.873,6.408-6.418,6.418c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418C49.645,8.998,52.518,11.871,52.518,15.416z"/> +</g> +<g id="Layer_2" display="none"> + <g display="inline"> + <path fill="#F06725" d="M99.814,64.673V46.052h13.125v18.621H99.814z M120,64.673v43.361H92.758V64.673H120z"/> + <rect y="50.855" fill="#F06725" width="9.017" height="57.181"/> + <path fill="#F06725" d="M53.04,108.034V55.046h16.718v52.988H53.04z M58.535,55.046V44.188h5.724v10.858H58.535z"/> + <rect x="18.373" y="30.743" fill="#646767" width="3.105" height="4.451"/> + <rect x="29.501" y="30.743" fill="#646767" width="3.105" height="4.451"/> + <rect x="40.586" y="30.743" fill="#646767" width="3.104" height="4.451"/> + <rect x="18.373" y="43.411" fill="#646767" width="3.105" height="4.503"/> + <rect x="29.501" y="43.411" fill="#646767" width="3.105" height="4.503"/> + <rect x="40.609" y="43.411" fill="#646767" width="3.104" height="4.503"/> + <rect x="18.373" y="56.082" fill="#646767" width="3.105" height="4.5"/> + <rect x="29.501" y="56.082" fill="#646767" width="3.105" height="4.5"/> + <rect x="40.609" y="56.082" fill="#646767" width="3.104" height="4.5"/> + <rect x="18.373" y="68.752" fill="#646767" width="3.105" height="4.501"/> + <rect x="29.501" y="68.752" fill="#646767" width="3.105" height="4.501"/> + <rect x="40.609" y="68.752" fill="#646767" width="3.104" height="4.501"/> + <rect x="18.373" y="81.422" fill="#646767" width="3.105" height="4.491"/> + <rect x="29.501" y="81.422" fill="#646767" width="3.105" height="4.491"/> + <rect x="40.586" y="81.422" fill="#646767" width="3.104" height="4.491"/> + <rect x="18.373" y="94.091" fill="#646767" width="3.105" height="4.504"/> + <rect x="29.501" y="94.091" fill="#646767" width="3.105" height="4.504"/> + <rect x="40.609" y="94.091" fill="#646767" width="3.104" height="4.504"/> + <path fill="#646767" d="M94.309,106.481V55.761h-4.378V33.247h-7.132V16.168h-3.105v17.079h-7.112v22.514h-4.378v50.721h-13.61 + V19.75h-8.655V9.006h-8.859V0H24.977v9.006h-8.859V19.75H7.483v86.731H0v3.104h7.483h47.109h13.61h26.105H120v-3.104H94.309z + M75.687,36.353h11.139V55.74H75.687V36.353z M28.082,3.105h5.892v5.9h-5.892V3.105z M19.221,12.11h5.756h12.102h5.754v7.64 + H19.221V12.11z M10.588,106.481V22.854h5.53h29.82h5.548v83.627H10.588z M71.309,106.481V58.868h19.896v47.613H71.309z"/> + </g> +</g> +<g id="Layer_3"> + <path fill="#636768" d="M2.66,104.889h112.106c1.47,0,2.66-1.194,2.66-2.662c0-1.473-1.19-2.663-2.66-2.663H2.66 + c-1.471,0-2.661,1.19-2.661,2.663C0,103.694,1.189,104.889,2.66,104.889z"/> + <rect x="2.66" y="52.421" fill="#F06725" width="12.314" height="39.343"/> + <rect x="102.453" y="31.996" fill="#F06725" width="12.313" height="59.768"/> + <rect x="35.928" y="31.25" fill="#F06725" width="12.315" height="60.514"/> + <rect x="69.187" y="50.168" fill="#F06725" width="12.323" height="41.596"/> + <circle fill="#F06725" cx="42.038" cy="11.39" r="3.76"/> + <circle fill="#F06725" cx="108.608" cy="12.135" r="3.76"/> + <path fill="#656668" d="M108.61,0.744c-6.28,0-11.387,5.11-11.387,11.389c0,0.877,0.107,1.729,0.293,2.55l-16.983,9.726 + c-1.392-1.221-3.194-1.988-5.187-1.988c-1.931,0-3.681,0.728-5.054,1.882L53.252,13.614c0.145-0.722,0.222-1.464,0.222-2.227 + C53.474,5.109,48.364,0,42.085,0c-6.281,0-11.39,5.109-11.39,11.391c0,1.168,0.178,2.296,0.508,3.36l-18.246,11.1 + c-1.208-0.748-2.617-1.202-4.138-1.202c-4.363,0-7.913,3.549-7.913,7.911c0,4.361,3.549,7.911,7.913,7.911 + c2.113,0,4.101-0.823,5.593-2.317c1.492-1.493,2.315-3.48,2.315-5.593c0-0.881-0.177-1.712-0.444-2.504l17.646-10.733 + c2.07,2.129,4.959,3.457,8.156,3.457c3.601,0,6.81-1.684,8.9-4.301l16.61,10.419c-0.086,0.463-0.146,0.938-0.146,1.422 + c-0.003,4.353,3.534,7.899,7.889,7.908h0.004c4.361,0,7.913-3.55,7.913-7.917c-0.003-0.43-0.063-0.841-0.128-1.251l16.774-9.605 + c2.085,2.485,5.217,4.069,8.71,4.069c6.28,0,11.388-5.11,11.388-11.39C119.998,5.855,114.889,0.744,108.61,0.744z M10.648,34.389 + c-0.488,0.49-1.139,0.758-1.83,0.758c-1.427,0-2.587-1.161-2.587-2.587c0-1.427,1.16-2.588,2.587-2.588 + c1.426,0,2.587,1.161,2.587,2.588C11.405,33.25,11.137,33.901,10.648,34.389z M42.085,17.456c-3.345,0-6.065-2.721-6.065-6.065 + c0-3.345,2.723-6.068,6.065-6.068c3.343,0,6.066,2.723,6.066,6.068C48.151,14.733,45.43,17.456,42.085,17.456z M75.347,32.903 + c-1.418-0.001-2.571-1.16-2.571-2.58c0-1.419,1.157-2.576,2.578-2.578c1.421,0,2.578,1.155,2.578,2.569 + C77.928,31.742,76.771,32.902,75.347,32.903z M108.61,18.2c-3.345,0-6.066-2.723-6.066-6.065c0-3.344,2.722-6.066,6.066-6.066 + c3.344,0,6.066,2.723,6.066,6.066C114.677,15.478,111.954,18.2,108.61,18.2z"/> +</g> +</svg> diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module.less index f6e922fccb8c75c7c8bd3427bad3d4b69036d257..5ae2a458ad40d3768f62a0a18b5b69cd44e18f7e 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module.less @@ -19,3 +19,4 @@ @import 'module/pages/_dashboard.less'; @import 'module/pages/_login.less'; @import 'module/pages/_cache-management.less'; +@import 'module/pages/_access-denied.less'; diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_access-denied.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_access-denied.less new file mode 100644 index 0000000000000000000000000000000000000000..8597dac7147de21efed34b6e2e77079c51939100 --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_access-denied.less @@ -0,0 +1,34 @@ +// /** +// * Copyright © Magento, Inc. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Access Error Page +// --------------------------------------------- + +.access-denied-hr { + height: 0.2rem; + border: 0; + box-shadow: 0 10px 10px -10px #b4b3b3 inset; +} + +.access-denied-page { + margin: 3.5rem 0 10rem 0; + + h2 { + margin-bottom: 3rem; + } + + ul { + li { + font-size: @font-size__s; + margin: 2rem 0 2rem 3rem; + + span { + font-size: @font-size__base; + margin-left: 1rem; + } + } + } +} diff --git a/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less new file mode 100644 index 0000000000000000000000000000000000000000..81c9b70c7188973b048980d31bf9b54acdabcda2 --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less @@ -0,0 +1,127 @@ +// /** +// * Copyright © Magento, Inc. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Magento_ReleaseNotification Modal on dashboard +// --------------------------------------------- + +.release-notification-modal, .analytics-subscription-modal, .developer-experience-modal, .b2b-modal { + -webkit-transition: visibility 0s .5s, opacity .5s ease; + transition: visibility 0s .5s, opacity .5s ease; + + &._show { + visibility: visible; + opacity: 1; + -webkit-transition: opacity .5s ease; + transition: opacity .5s ease; + } + + .modal-inner-wrap { + -webkit-transform: translateX(0); + transform: translateX(0); + -webkit-transition: -webkit-transform 0s; + transition: transform 0s; + height: 50rem; + max-width: 75rem; + margin-top: 13rem; + + .modal-content, .modal-header { + padding-left: 4rem; + padding-right: 4rem; + + .action-close { + display: none; + } + } + } + + .admin__fieldset { + padding: 0; + } +} + +.release-notification-text { + line-height: @line-height__l; + + ul { + margin: 2rem 0 2rem 0; + + li { + font-size: @font-size__xs; + margin: 1.5rem 0 1.5rem 2rem; + + span { + font-size: @font-size__base; + margin-left: 2rem; + } + } + } +} + +.release-notification-button-next, .release-notification-button-back { + display: inline-block; + vertical-align: top; + float: right; + position: absolute; + bottom: 4rem; +} + +.release-notification-button-next { + right: 4rem; +} + +.analytics-highlight { + background: url("Magento_ReleaseNotification::images/analytics-icon.svg") no-repeat; + background-size: 65px 58px; +} + +.b2b-highlight { + background: url("Magento_ReleaseNotification::images/b2b-icon.svg") no-repeat; + background-size: 65px 53.37px; +} + +.developer-experience-highlight { + background: url("Magento_ReleaseNotification::images/developer-experience-icon.svg") no-repeat; + background-size: 65px 59px; +} + +.analytics-highlight, .b2b-highlight, .developer-experience-highlight { + padding: 0 0 2rem 8.5rem; + margin-left: 1rem; + + h3 { + margin: 0; + + span { + font-style: @font-style__emphasis; + font-size: @font-size__s; + font-weight: @font-weight__light; + } + } +} + +.analytics-subscription-modal { + h1:first-of-type { + background: url("Magento_ReleaseNotification::images/analytics-icon.svg") no-repeat; + background-size: 55px 49.08px; + padding: 1.5rem 0 2rem 7rem; + } +} + +.b2b-modal { + h1:first-of-type { + background: url("Magento_ReleaseNotification::images/b2b-icon.svg") no-repeat; + background-size: 55px 49.92px; + padding: 1.5rem 0 2rem 7rem; + } +} + +.developer-experience-modal { + h1:first-of-type { + background: url("Magento_ReleaseNotification::images/developer-experience-icon.svg") no-repeat; + background-size: 55px 46px; + padding: 1.5rem 0 2rem 7rem; + } +} diff --git a/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/analytics-icon.svg b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/analytics-icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..fde91d775d444924774ba1f2d3907c529863dd2d --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/analytics-icon.svg @@ -0,0 +1,84 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="120px" + height="105px" viewBox="0 0 120 105" enable-background="new 0 0 120 105" xml:space="preserve"> +<g id="Layer_1" display="none"> + <rect x="2.904" y="27.946" display="inline" fill="#F06524" width="113.745" height="12.408"/> + <path display="inline" fill="#636667" d="M95.811,85.511c-0.753-2.551-1.775-5.013-3.059-7.344 + c-0.207-0.387-0.134-0.884,0.191-1.208c2.857-2.869,2.857-7.489,0.013-10.346l-3.649-3.686c-2.873-2.86-7.493-2.86-10.348-0.016 + c-0.334,0.336-0.828,0.409-1.228,0.193c-2.321-1.271-4.785-2.295-7.337-3.047c-0.419-0.126-0.714-0.525-0.712-0.979 + c0.004-1.947-0.766-3.812-2.139-5.187c-1.37-1.377-3.233-2.148-5.176-2.148h-5.184c-4.039,0-7.312,3.274-7.312,7.314 + c0.001,0.464-0.294,0.863-0.728,0.993c-2.537,0.747-5.002,1.771-7.335,3.051c-0.391,0.213-0.882,0.143-1.199-0.178 + c-1.377-1.381-3.236-2.15-5.176-2.15c-1.938,0-3.799,0.77-5.168,2.143l-3.688,3.744c-2.833,2.845-2.833,7.465,0.011,10.321 + c0.336,0.336,0.41,0.835,0.191,1.24c-1.269,2.312-2.294,4.772-3.045,7.324c-0.487,1.657,0.46,3.396,2.117,3.885 + c1.658,0.487,3.396-0.46,3.884-2.117c0.624-2.12,1.475-4.164,2.538-6.101c1.544-2.863,1.027-6.375-1.262-8.653 + c-0.405-0.407-0.405-1.082,0.009-1.497l3.686-3.742c0.181-0.182,0.449-0.291,0.727-0.291c0.279,0,0.545,0.109,0.743,0.307 + c2.271,2.289,5.794,2.805,8.634,1.253c1.944-1.065,3.993-1.918,6.115-2.541c3.105-0.933,5.213-3.781,5.2-7.005 + C56.126,58.473,56.6,58,57.184,58h5.184c0.281,0,0.55,0.112,0.75,0.31c0.199,0.2,0.309,0.471,0.309,0.752 + c-0.012,3.217,2.098,6.065,5.185,6.992c2.138,0.631,4.187,1.482,6.129,2.545c2.854,1.546,6.369,1.026,8.646-1.269 + c0.403-0.401,1.075-0.401,1.492,0.015l3.639,3.675c0.42,0.424,0.42,1.098,0.007,1.515c-2.283,2.265-2.802,5.783-1.261,8.628 + c1.071,1.952,1.922,3.997,2.545,6.116c0.489,1.657,2.228,2.604,3.886,2.116C95.351,88.907,96.297,87.169,95.811,85.511z"/> + <path display="inline" fill="#636667" d="M118.29,1.486C117.337,0.536,116.046,0,114.7,0H4.843 + C2.035,0.014-0.224,2.283-0.224,5.076v89.103c-0.006,1.334,0.524,2.629,1.475,3.584c0.951,0.955,2.244,1.492,3.592,1.492h109.851 + c1.35,0.005,2.645-0.529,3.6-1.484c0.955-0.954,1.49-2.254,1.482-3.605V5.076C119.776,3.73,119.243,2.439,118.29,1.486z + M113.521,6.255v18.564H6.032V6.255H113.521z M41.2,93c1.493-8.922,9.23-15.729,18.576-15.729c9.348,0,17.083,6.808,18.576,15.729 + H41.2z M84.673,93c-1.542-12.39-12.089-21.984-24.896-21.984C46.97,71.016,36.422,80.61,34.879,93H6.032V31.074h107.49V93H84.673z" + /> + <path display="inline" fill="#636667" d="M21.661,19.955c-1.204,1.203-2.836,1.879-4.539,1.879c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418s6.418,2.873,6.418,6.418C23.541,17.118,22.865,18.751,21.661,19.955z"/> + <circle display="inline" fill="#636667" cx="31.611" cy="15.416" r="6.418"/> + <path display="inline" fill="#636667" d="M52.518,15.416c-0.009,3.545-2.873,6.408-6.418,6.418c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418C49.645,8.998,52.518,11.871,52.518,15.416z"/> +</g> +<g id="Layer_2" display="none"> + <g display="inline"> + <path fill="#F06725" d="M99.814,64.673V46.052h13.125v18.621H99.814z M120,64.673v43.361H92.758V64.673H120z"/> + <rect y="50.855" fill="#F06725" width="9.017" height="57.181"/> + <path fill="#F06725" d="M53.04,108.034V55.046h16.718v52.988H53.04z M58.535,55.046V44.188h5.724v10.858H58.535z"/> + <rect x="18.373" y="30.743" fill="#646767" width="3.105" height="4.451"/> + <rect x="29.501" y="30.743" fill="#646767" width="3.105" height="4.451"/> + <rect x="40.586" y="30.743" fill="#646767" width="3.104" height="4.451"/> + <rect x="18.373" y="43.411" fill="#646767" width="3.105" height="4.503"/> + <rect x="29.501" y="43.411" fill="#646767" width="3.105" height="4.503"/> + <rect x="40.609" y="43.411" fill="#646767" width="3.104" height="4.503"/> + <rect x="18.373" y="56.082" fill="#646767" width="3.105" height="4.5"/> + <rect x="29.501" y="56.082" fill="#646767" width="3.105" height="4.5"/> + <rect x="40.609" y="56.082" fill="#646767" width="3.104" height="4.5"/> + <rect x="18.373" y="68.752" fill="#646767" width="3.105" height="4.501"/> + <rect x="29.501" y="68.752" fill="#646767" width="3.105" height="4.501"/> + <rect x="40.609" y="68.752" fill="#646767" width="3.104" height="4.501"/> + <rect x="18.373" y="81.422" fill="#646767" width="3.105" height="4.491"/> + <rect x="29.501" y="81.422" fill="#646767" width="3.105" height="4.491"/> + <rect x="40.586" y="81.422" fill="#646767" width="3.104" height="4.491"/> + <rect x="18.373" y="94.091" fill="#646767" width="3.105" height="4.504"/> + <rect x="29.501" y="94.091" fill="#646767" width="3.105" height="4.504"/> + <rect x="40.609" y="94.091" fill="#646767" width="3.104" height="4.504"/> + <path fill="#646767" d="M94.309,106.481V55.761h-4.378V33.247h-7.132V16.168h-3.105v17.079h-7.112v22.514h-4.378v50.721h-13.61 + V19.75h-8.655V9.006h-8.859V0H24.977v9.006h-8.859V19.75H7.483v86.731H0v3.104h7.483h47.109h13.61h26.105H120v-3.104H94.309z + M75.687,36.353h11.139V55.74H75.687V36.353z M28.082,3.105h5.892v5.9h-5.892V3.105z M19.221,12.11h5.756h12.102h5.754v7.64 + H19.221V12.11z M10.588,106.481V22.854h5.53h29.82h5.548v83.627H10.588z M71.309,106.481V58.868h19.896v47.613H71.309z"/> + </g> +</g> +<g id="Layer_3"> + <path fill="#636768" d="M2.66,104.889h112.106c1.47,0,2.66-1.194,2.66-2.662c0-1.473-1.19-2.663-2.66-2.663H2.66 + c-1.471,0-2.661,1.19-2.661,2.663C0,103.694,1.189,104.889,2.66,104.889z"/> + <rect x="2.66" y="52.421" fill="#F06725" width="12.314" height="39.343"/> + <rect x="102.453" y="31.996" fill="#F06725" width="12.313" height="59.768"/> + <rect x="35.928" y="31.25" fill="#F06725" width="12.315" height="60.514"/> + <rect x="69.187" y="50.168" fill="#F06725" width="12.323" height="41.596"/> + <circle fill="#F06725" cx="42.038" cy="11.39" r="3.76"/> + <circle fill="#F06725" cx="108.608" cy="12.135" r="3.76"/> + <path fill="#656668" d="M108.61,0.744c-6.28,0-11.387,5.11-11.387,11.389c0,0.877,0.107,1.729,0.293,2.55l-16.983,9.726 + c-1.392-1.221-3.194-1.988-5.187-1.988c-1.931,0-3.681,0.728-5.054,1.882L53.252,13.614c0.145-0.722,0.222-1.464,0.222-2.227 + C53.474,5.109,48.364,0,42.085,0c-6.281,0-11.39,5.109-11.39,11.391c0,1.168,0.178,2.296,0.508,3.36l-18.246,11.1 + c-1.208-0.748-2.617-1.202-4.138-1.202c-4.363,0-7.913,3.549-7.913,7.911c0,4.361,3.549,7.911,7.913,7.911 + c2.113,0,4.101-0.823,5.593-2.317c1.492-1.493,2.315-3.48,2.315-5.593c0-0.881-0.177-1.712-0.444-2.504l17.646-10.733 + c2.07,2.129,4.959,3.457,8.156,3.457c3.601,0,6.81-1.684,8.9-4.301l16.61,10.419c-0.086,0.463-0.146,0.938-0.146,1.422 + c-0.003,4.353,3.534,7.899,7.889,7.908h0.004c4.361,0,7.913-3.55,7.913-7.917c-0.003-0.43-0.063-0.841-0.128-1.251l16.774-9.605 + c2.085,2.485,5.217,4.069,8.71,4.069c6.28,0,11.388-5.11,11.388-11.39C119.998,5.855,114.889,0.744,108.61,0.744z M10.648,34.389 + c-0.488,0.49-1.139,0.758-1.83,0.758c-1.427,0-2.587-1.161-2.587-2.587c0-1.427,1.16-2.588,2.587-2.588 + c1.426,0,2.587,1.161,2.587,2.588C11.405,33.25,11.137,33.901,10.648,34.389z M42.085,17.456c-3.345,0-6.065-2.721-6.065-6.065 + c0-3.345,2.723-6.068,6.065-6.068c3.343,0,6.066,2.723,6.066,6.068C48.151,14.733,45.43,17.456,42.085,17.456z M75.347,32.903 + c-1.418-0.001-2.571-1.16-2.571-2.58c0-1.419,1.157-2.576,2.578-2.578c1.421,0,2.578,1.155,2.578,2.569 + C77.928,31.742,76.771,32.902,75.347,32.903z M108.61,18.2c-3.345,0-6.066-2.723-6.066-6.065c0-3.344,2.722-6.066,6.066-6.066 + c3.344,0,6.066,2.723,6.066,6.066C114.677,15.478,111.954,18.2,108.61,18.2z"/> +</g> +</svg> diff --git a/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/b2b-icon.svg b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/b2b-icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..20552d6178c2550609c17196bd1e3ec1cb0b7f87 --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/b2b-icon.svg @@ -0,0 +1,60 @@ +<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="120px" + height="111px" viewBox="0 0 120 111" enable-background="new 0 0 120 111" xml:space="preserve"> +<g id="Layer_1" display="none"> + <rect x="2.904" y="27.946" display="inline" fill="#F06524" width="113.745" height="12.408"/> + <path display="inline" fill="#636667" d="M95.811,85.511c-0.753-2.551-1.775-5.013-3.059-7.344 + c-0.207-0.387-0.134-0.884,0.191-1.208c2.857-2.869,2.857-7.489,0.013-10.346l-3.649-3.686c-2.873-2.86-7.493-2.86-10.348-0.016 + c-0.334,0.336-0.828,0.409-1.228,0.193c-2.321-1.271-4.785-2.295-7.337-3.047c-0.419-0.126-0.714-0.525-0.712-0.979 + c0.004-1.947-0.766-3.812-2.139-5.187c-1.37-1.377-3.233-2.148-5.176-2.148h-5.184c-4.039,0-7.312,3.274-7.312,7.314 + c0.001,0.464-0.294,0.863-0.728,0.993c-2.537,0.747-5.002,1.771-7.335,3.051c-0.391,0.213-0.882,0.143-1.199-0.178 + c-1.377-1.381-3.236-2.15-5.176-2.15c-1.938,0-3.799,0.77-5.168,2.143l-3.688,3.744c-2.833,2.845-2.833,7.465,0.011,10.321 + c0.336,0.336,0.41,0.835,0.191,1.24c-1.269,2.312-2.294,4.772-3.045,7.324c-0.487,1.657,0.46,3.396,2.117,3.885 + c1.658,0.487,3.396-0.46,3.884-2.117c0.624-2.12,1.475-4.164,2.538-6.101c1.544-2.863,1.027-6.375-1.262-8.653 + c-0.405-0.407-0.405-1.082,0.009-1.497l3.686-3.742c0.181-0.182,0.449-0.291,0.727-0.291c0.279,0,0.545,0.109,0.743,0.307 + c2.271,2.289,5.794,2.805,8.634,1.253c1.944-1.065,3.993-1.918,6.115-2.541c3.105-0.933,5.213-3.781,5.2-7.005 + C56.126,58.473,56.6,58,57.184,58h5.184c0.281,0,0.55,0.112,0.75,0.31c0.199,0.2,0.309,0.471,0.309,0.752 + c-0.012,3.217,2.098,6.065,5.185,6.992c2.138,0.631,4.187,1.482,6.129,2.545c2.854,1.546,6.369,1.026,8.646-1.269 + c0.403-0.401,1.075-0.401,1.492,0.015l3.639,3.675c0.42,0.424,0.42,1.098,0.007,1.515c-2.283,2.265-2.802,5.783-1.261,8.628 + c1.071,1.952,1.922,3.997,2.545,6.116c0.489,1.657,2.228,2.604,3.886,2.116C95.351,88.907,96.297,87.169,95.811,85.511z"/> + <path display="inline" fill="#636667" d="M118.29,1.486C117.337,0.536,116.046,0,114.7,0H4.843 + C2.035,0.014-0.224,2.283-0.224,5.076v89.103c-0.006,1.334,0.524,2.629,1.475,3.584c0.951,0.955,2.244,1.492,3.592,1.492h109.851 + c1.35,0.005,2.645-0.529,3.6-1.484c0.955-0.954,1.49-2.254,1.482-3.605V5.076C119.776,3.73,119.243,2.439,118.29,1.486z + M113.521,6.255v18.564H6.032V6.255H113.521z M41.2,93c1.493-8.922,9.23-15.729,18.576-15.729c9.348,0,17.083,6.808,18.576,15.729 + H41.2z M84.673,93c-1.542-12.39-12.089-21.984-24.896-21.984C46.97,71.016,36.422,80.61,34.879,93H6.032V31.074h107.49V93H84.673z" + /> + <path display="inline" fill="#636667" d="M21.661,19.955c-1.204,1.203-2.836,1.879-4.539,1.879c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418s6.418,2.873,6.418,6.418C23.541,17.118,22.865,18.751,21.661,19.955z"/> + <circle display="inline" fill="#636667" cx="31.611" cy="15.416" r="6.418"/> + <path display="inline" fill="#636667" d="M52.518,15.416c-0.009,3.545-2.873,6.408-6.418,6.418c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418C49.645,8.998,52.518,11.871,52.518,15.416z"/> +</g> +<g id="Layer_2"> + <g> + <path fill="#F06725" d="M99.814,64.673V46.052h13.125v18.621H99.814z M120,64.673v43.361H92.758V64.673H120z"/> + <rect y="50.855" fill="#F06725" width="9.017" height="57.181"/> + <path fill="#F06725" d="M53.04,108.034V55.046h16.718v52.988H53.04z M58.535,55.046V44.188h5.724v10.858H58.535z"/> + <rect x="18.373" y="30.743" fill="#646767" width="3.105" height="4.451"/> + <rect x="29.501" y="30.743" fill="#646767" width="3.105" height="4.451"/> + <rect x="40.586" y="30.743" fill="#646767" width="3.104" height="4.451"/> + <rect x="18.373" y="43.411" fill="#646767" width="3.105" height="4.503"/> + <rect x="29.501" y="43.411" fill="#646767" width="3.105" height="4.503"/> + <rect x="40.609" y="43.411" fill="#646767" width="3.104" height="4.503"/> + <rect x="18.373" y="56.082" fill="#646767" width="3.105" height="4.5"/> + <rect x="29.501" y="56.082" fill="#646767" width="3.105" height="4.5"/> + <rect x="40.609" y="56.082" fill="#646767" width="3.104" height="4.5"/> + <rect x="18.373" y="68.752" fill="#646767" width="3.105" height="4.501"/> + <rect x="29.501" y="68.752" fill="#646767" width="3.105" height="4.501"/> + <rect x="40.609" y="68.752" fill="#646767" width="3.104" height="4.501"/> + <rect x="18.373" y="81.422" fill="#646767" width="3.105" height="4.491"/> + <rect x="29.501" y="81.422" fill="#646767" width="3.105" height="4.491"/> + <rect x="40.586" y="81.422" fill="#646767" width="3.104" height="4.491"/> + <rect x="18.373" y="94.091" fill="#646767" width="3.105" height="4.504"/> + <rect x="29.501" y="94.091" fill="#646767" width="3.105" height="4.504"/> + <rect x="40.609" y="94.091" fill="#646767" width="3.104" height="4.504"/> + <path fill="#646767" d="M94.309,106.481V55.761h-4.378V33.247h-7.132V16.168h-3.105v17.079h-7.112v22.514h-4.378v50.721h-13.61 + V19.75h-8.655V9.006h-8.859V0H24.977v9.006h-8.859V19.75H7.483v86.731H0v3.104h7.483h47.109h13.61h26.105H120v-3.104H94.309z + M75.687,36.353h11.139V55.74H75.687V36.353z M28.082,3.105h5.892v5.9h-5.892V3.105z M19.221,12.11h5.756h12.102h5.754v7.64 + H19.221V12.11z M10.588,106.481V22.854h5.53h29.82h5.548v83.627H10.588z M71.309,106.481V58.868h19.896v47.613H71.309z"/> + </g> +</g> +</svg> diff --git a/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/developer-experience-icon.svg b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/developer-experience-icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..50d208118aedf9a29e998659bd99258a1e1a5bb2 --- /dev/null +++ b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/images/developer-experience-icon.svg @@ -0,0 +1,28 @@ +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="120px" height="100px" viewBox="0 0 120 100" enable-background="new 0 0 120 100" xml:space="preserve"> +<rect x="2.904" y="27.946" fill="#F06524" width="113.745" height="12.408"/> +<path fill="#636667" d="M95.811,85.511c-0.753-2.551-1.775-5.013-3.059-7.344c-0.207-0.387-0.134-0.884,0.191-1.208 + c2.857-2.869,2.857-7.489,0.013-10.346l-3.649-3.686c-2.873-2.86-7.493-2.86-10.348-0.016c-0.334,0.336-0.828,0.409-1.228,0.193 + c-2.321-1.271-4.785-2.295-7.337-3.047c-0.419-0.126-0.714-0.525-0.712-0.979c0.004-1.947-0.766-3.812-2.139-5.187 + c-1.37-1.377-3.233-2.148-5.176-2.148h-5.184c-4.039,0-7.312,3.274-7.312,7.314c0.001,0.464-0.294,0.863-0.728,0.993 + c-2.537,0.747-5.002,1.771-7.335,3.051c-0.391,0.213-0.882,0.143-1.199-0.178c-1.377-1.381-3.236-2.15-5.176-2.15 + c-1.938,0-3.799,0.77-5.168,2.143l-3.688,3.744c-2.833,2.845-2.833,7.465,0.011,10.321c0.336,0.336,0.41,0.835,0.191,1.24 + c-1.269,2.312-2.294,4.772-3.045,7.324c-0.487,1.657,0.46,3.396,2.117,3.885c1.658,0.487,3.396-0.46,3.884-2.117 + c0.624-2.12,1.475-4.164,2.538-6.101c1.544-2.863,1.027-6.375-1.262-8.653c-0.405-0.407-0.405-1.082,0.009-1.497l3.686-3.742 + c0.181-0.182,0.449-0.291,0.727-0.291c0.279,0,0.545,0.109,0.743,0.307c2.271,2.289,5.794,2.805,8.634,1.253 + c1.944-1.065,3.993-1.918,6.115-2.541c3.105-0.933,5.213-3.781,5.2-7.005C56.126,58.473,56.6,58,57.184,58h5.184 + c0.281,0,0.55,0.112,0.75,0.31c0.199,0.2,0.309,0.471,0.309,0.752c-0.012,3.217,2.098,6.065,5.185,6.992 + c2.138,0.631,4.187,1.482,6.129,2.545c2.854,1.546,6.369,1.026,8.646-1.269c0.403-0.401,1.075-0.401,1.492,0.015l3.639,3.675 + c0.42,0.424,0.42,1.098,0.007,1.515c-2.283,2.265-2.802,5.783-1.261,8.628c1.071,1.952,1.922,3.997,2.545,6.116 + c0.489,1.657,2.228,2.604,3.886,2.116C95.351,88.907,96.297,87.169,95.811,85.511z"/> +<path fill="#636667" d="M118.29,1.486C117.337,0.536,116.046,0,114.7,0H4.843C2.035,0.014-0.224,2.283-0.224,5.076v89.103 + c-0.006,1.334,0.524,2.629,1.475,3.584c0.951,0.955,2.244,1.492,3.592,1.492h109.851c1.35,0.005,2.645-0.529,3.6-1.484 + c0.955-0.954,1.49-2.254,1.482-3.605V5.076C119.776,3.73,119.243,2.439,118.29,1.486z M113.521,6.255v18.564H6.032V6.255H113.521z + M41.2,93c1.493-8.922,9.23-15.729,18.576-15.729c9.348,0,17.083,6.808,18.576,15.729H41.2z M84.673,93 + c-1.542-12.39-12.089-21.984-24.896-21.984C46.97,71.016,36.422,80.61,34.879,93H6.032V31.074h107.49V93H84.673z"/> +<path fill="#636667" d="M21.661,19.955c-1.204,1.203-2.836,1.879-4.539,1.879c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418s6.418,2.873,6.418,6.418C23.541,17.118,22.865,18.751,21.661,19.955z"/> +<circle fill="#636667" cx="31.611" cy="15.416" r="6.418"/> +<path fill="#636667" d="M52.518,15.416c-0.009,3.545-2.873,6.408-6.418,6.418c-3.545,0-6.419-2.873-6.419-6.418 + s2.874-6.418,6.419-6.418C49.645,8.998,52.518,11.871,52.518,15.416z"/> +</svg> diff --git a/composer.json b/composer.json index b7826101376f446e2f4e712a63f7d4d48e0cdc79..c56278203ab219732c0fbd01b8a1389ee548dd01 100644 --- a/composer.json +++ b/composer.json @@ -85,6 +85,7 @@ "magento/module-marketplace": "100.2.0", "magento/module-admin-notification": "100.2.0", "magento/module-advanced-pricing-import-export": "100.2.0", + "magento/module-analytics": "100.2.0-dev", "magento/module-authorization": "100.2.0", "magento/module-authorizenet": "100.2.0", "magento/module-backend": "100.2.0", @@ -95,6 +96,7 @@ "magento/module-cache-invalidate": "100.2.0", "magento/module-captcha": "100.2.0", "magento/module-catalog": "102.0.0", + "magento/module-catalog-analytics": "100.2.0-dev", "magento/module-catalog-import-export": "100.2.0", "magento/module-catalog-inventory": "100.2.0", "magento/module-catalog-rule": "101.0.0", @@ -115,6 +117,7 @@ "magento/module-cron": "100.2.0", "magento/module-currency-symbol": "100.2.0", "magento/module-customer": "101.0.0", + "magento/module-customer-analytics": "100.2.0-dev", "magento/module-customer-import-export": "100.2.0", "magento/module-deploy": "100.2.0", "magento/module-developer": "100.2.0", @@ -151,13 +154,17 @@ "magento/module-product-alert": "100.2.0", "magento/module-product-video": "100.2.0", "magento/module-quote": "101.0.0", + "magento/module-quote-analytics": "100.2.0-dev", + "magento/module-release-notification": "100.2.0", "magento/module-reports": "100.2.0", "magento/module-require-js": "100.2.0", "magento/module-review": "100.2.0", + "magento/module-review-analytics": "100.2.0-dev", "magento/module-robots": "100.2.0", "magento/module-rss": "100.2.0", "magento/module-rule": "100.2.0", "magento/module-sales": "101.0.0", + "magento/module-sales-analytics": "100.2.0-dev", "magento/module-sales-inventory": "100.2.0", "magento/module-sales-rule": "101.0.0", "magento/module-sales-sequence": "100.2.0", @@ -189,6 +196,7 @@ "magento/module-weee": "100.2.0", "magento/module-widget": "101.0.0", "magento/module-wishlist": "101.0.0", + "magento/module-wishlist-analytics": "100.2.0-dev", "magento/theme-adminhtml-backend": "100.2.0", "magento/theme-frontend-blank": "100.2.0", "magento/theme-frontend-luma": "100.2.0", diff --git a/composer.lock b/composer.lock index 31307900a6bc25fa76ad4d51dbf6b923ecbf68d0..32b3598bf0034715f69c5cbbbe20737852191834 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "adc6c15c236190cb2759da1721a394e0", + "hash": "057bdfbdba89c00c7240cddfa78527c4", + "content-hash": "738943bccb2a2949bdf94f661823f6b4", "packages": [ { "name": "braintree/braintree_php", @@ -51,7 +52,7 @@ } ], "description": "Braintree PHP Client Library", - "time": "2017-02-16T19:59:04+00:00" + "time": "2017-02-16 19:59:04" }, { "name": "colinmollenhour/cache-backend-file", @@ -87,7 +88,7 @@ ], "description": "The stock Zend_Cache_Backend_File backend has extremely poor performance for cleaning by tags making it become unusable as the number of cached items increases. This backend makes many changes resulting in a huge performance boost, especially for tag cleaning.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_File", - "time": "2016-05-02T16:24:47+00:00" + "time": "2016-05-02 16:24:47" }, { "name": "colinmollenhour/cache-backend-redis", @@ -123,7 +124,7 @@ ], "description": "Zend_Cache backend using Redis with full support for tags.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis", - "time": "2017-03-25T04:54:24+00:00" + "time": "2017-03-25 04:54:24" }, { "name": "colinmollenhour/credis", @@ -163,7 +164,7 @@ ], "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", "homepage": "https://github.com/colinmollenhour/credis", - "time": "2017-07-05T15:32:38+00:00" + "time": "2017-07-05 15:32:38" }, { "name": "colinmollenhour/php-redis-session-abstract", @@ -200,7 +201,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2017-03-22T16:13:03+00:00" + "time": "2017-03-22 16:13:03" }, { "name": "composer/ca-bundle", @@ -259,7 +260,7 @@ "ssl", "tls" ], - "time": "2017-09-11T07:24:36+00:00" + "time": "2017-09-11 07:24:36" }, { "name": "composer/composer", @@ -336,7 +337,7 @@ "dependency", "package" ], - "time": "2017-03-10T08:29:45+00:00" + "time": "2017-03-10 08:29:45" }, { "name": "composer/semver", @@ -398,7 +399,7 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2016-08-30 16:08:34" }, { "name": "composer/spdx-licenses", @@ -459,7 +460,7 @@ "spdx", "validator" ], - "time": "2017-04-03T19:08:52+00:00" + "time": "2017-04-03 19:08:52" }, { "name": "container-interop/container-interop", @@ -490,20 +491,20 @@ ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop", - "time": "2017-02-14T19:40:03+00:00" + "time": "2017-02-14 19:40:03" }, { "name": "justinrainbow/json-schema", - "version": "5.2.1", + "version": "5.2.6", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "429be236f296ca249d61c65649cdf2652f4a5e80" + "reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/429be236f296ca249d61c65649cdf2652f4a5e80", - "reference": "429be236f296ca249d61c65649cdf2652f4a5e80", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/d283e11b6e14c6f4664cf080415c4341293e5bbd", + "reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd", "shasum": "" }, "require": { @@ -512,7 +513,6 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.1", "json-schema/json-schema-test-suite": "1.2.0", - "phpdocumentor/phpdocumentor": "^2.7", "phpunit/phpunit": "^4.8.22" }, "bin": [ @@ -557,7 +557,7 @@ "json", "schema" ], - "time": "2017-05-16T21:06:09+00:00" + "time": "2017-10-21 13:15:38" }, { "name": "league/climate", @@ -606,7 +606,7 @@ "php", "terminal" ], - "time": "2015-01-18T14:31:58+00:00" + "time": "2015-01-18 14:31:58" }, { "name": "magento/composer", @@ -642,7 +642,7 @@ "AFL-3.0" ], "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2017-04-24T09:57:02+00:00" + "time": "2017-04-24 09:57:02" }, { "name": "magento/magento-composer-installer", @@ -721,7 +721,7 @@ "composer-installer", "magento" ], - "time": "2016-10-06T16:05:07+00:00" + "time": "2016-10-06 16:05:07" }, { "name": "magento/zendframework1", @@ -768,7 +768,7 @@ "ZF1", "framework" ], - "time": "2017-06-21T14:56:23+00:00" + "time": "2017-06-21 14:56:23" }, { "name": "monolog/monolog", @@ -846,7 +846,7 @@ "logging", "psr-3" ], - "time": "2017-06-19T01:22:40+00:00" + "time": "2017-06-19 01:22:40" }, { "name": "oyejorge/less.php", @@ -908,20 +908,20 @@ "php", "stylesheet" ], - "time": "2017-03-28T22:19:25+00:00" + "time": "2017-03-28 22:19:25" }, { "name": "paragonie/random_compat", - "version": "v2.0.10", + "version": "v2.0.11", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", "shasum": "" }, "require": { @@ -956,7 +956,7 @@ "pseudorandom", "random" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2017-09-27 21:40:39" }, { "name": "pelago/emogrifier", @@ -1012,20 +1012,20 @@ ], "description": "Converts CSS styles into inline style attributes in your HTML code", "homepage": "http://www.pelagodesign.com/sidecar/emogrifier/", - "time": "2015-05-15T11:37:51+00:00" + "time": "2015-05-15 11:37:51" }, { "name": "phpseclib/phpseclib", - "version": "2.0.6", + "version": "2.0.7", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa" + "reference": "f4b6a522dfa1fd1e477c9cfe5909d5b31f098c0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/34a7699e6f31b1ef4035ee36444407cecf9f56aa", - "reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/f4b6a522dfa1fd1e477c9cfe5909d5b31f098c0b", + "reference": "f4b6a522dfa1fd1e477c9cfe5909d5b31f098c0b", "shasum": "" }, "require": { @@ -1104,7 +1104,7 @@ "x.509", "x509" ], - "time": "2017-06-05T06:31:10+00:00" + "time": "2017-10-23 05:04:54" }, { "name": "psr/container", @@ -1153,7 +1153,7 @@ "container-interop", "psr" ], - "time": "2017-02-14T16:28:37+00:00" + "time": "2017-02-14 16:28:37" }, { "name": "psr/log", @@ -1200,7 +1200,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2016-10-10 12:19:37" }, { "name": "ramsey/uuid", @@ -1282,7 +1282,7 @@ "identifier", "uuid" ], - "time": "2017-03-26T20:37:53+00:00" + "time": "2017-03-26 20:37:53" }, { "name": "seld/cli-prompt", @@ -1330,7 +1330,7 @@ "input", "prompt" ], - "time": "2017-03-18T11:32:45+00:00" + "time": "2017-03-18 11:32:45" }, { "name": "seld/jsonlint", @@ -1379,7 +1379,7 @@ "parser", "validator" ], - "time": "2017-06-18T15:11:04+00:00" + "time": "2017-06-18 15:11:04" }, { "name": "seld/phar-utils", @@ -1423,7 +1423,7 @@ "keywords": [ "phra" ], - "time": "2015-10-13T18:44:15+00:00" + "time": "2015-10-13 18:44:15" }, { "name": "sjparkinson/static-review", @@ -1476,20 +1476,20 @@ } ], "description": "An extendable framework for version control hooks.", - "time": "2014-09-22T08:40:36+00:00" + "time": "2014-09-22 08:40:36" }, { "name": "symfony/console", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c0807a2ca978e64d8945d373a9221a5c35d1a253" + "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c0807a2ca978e64d8945d373a9221a5c35d1a253", - "reference": "c0807a2ca978e64d8945d373a9221a5c35d1a253", + "url": "https://api.github.com/repos/symfony/console/zipball/f81549d2c5fdee8d711c9ab3c7e7362353ea5853", + "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853", "shasum": "" }, "require": { @@ -1537,7 +1537,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-08-27T14:29:03+00:00" + "time": "2017-10-01 21:00:16" }, { "name": "symfony/debug", @@ -1594,20 +1594,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-07-30T07:22:48+00:00" + "time": "2016-07-30 07:22:48" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d" + "reference": "7fe089232554357efb8d4af65ce209fc6e5a2186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d", - "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fe089232554357efb8d4af65ce209fc6e5a2186", + "reference": "7fe089232554357efb8d4af65ce209fc6e5a2186", "shasum": "" }, "require": { @@ -1654,20 +1654,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-06-02T07:47:27+00:00" + "time": "2017-10-01 21:00:16" }, { "name": "symfony/filesystem", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb" + "reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b32a0e5f928d0fa3d1dd03c78d020777e50c10cb", - "reference": "b32a0e5f928d0fa3d1dd03c78d020777e50c10cb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/90bc45abf02ae6b7deb43895c1052cb0038506f1", + "reference": "90bc45abf02ae6b7deb43895c1052cb0038506f1", "shasum": "" }, "require": { @@ -1703,20 +1703,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-07-29T21:54:42+00:00" + "time": "2017-10-03 13:33:10" }, { "name": "symfony/finder", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e" + "reference": "773e19a491d97926f236942484cb541560ce862d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/b2260dbc80f3c4198f903215f91a1ac7fe9fe09e", - "reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e", + "url": "https://api.github.com/repos/symfony/finder/zipball/773e19a491d97926f236942484cb541560ce862d", + "reference": "773e19a491d97926f236942484cb541560ce862d", "shasum": "" }, "require": { @@ -1752,20 +1752,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-07-29T21:54:42+00:00" + "time": "2017-10-02 06:42:24" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", - "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "shasum": "" }, "require": { @@ -1777,7 +1777,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1811,20 +1811,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/process", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8" + "reference": "26c9fb02bf06bd6b90f661a5bd17e510810d0176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8", - "reference": "57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8", + "url": "https://api.github.com/repos/symfony/process/zipball/26c9fb02bf06bd6b90f661a5bd17e510810d0176", + "reference": "26c9fb02bf06bd6b90f661a5bd17e510810d0176", "shasum": "" }, "require": { @@ -1860,7 +1860,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-07-03T08:04:30+00:00" + "time": "2017-10-01 21:00:16" }, { "name": "tedivm/jshrink", @@ -1906,7 +1906,7 @@ "javascript", "minifier" ], - "time": "2015-07-04T07:35:09+00:00" + "time": "2015-07-04 07:35:09" }, { "name": "tubalmartin/cssmin", @@ -1959,7 +1959,7 @@ "minify", "yui" ], - "time": "2017-05-16T13:45:26+00:00" + "time": "2017-05-16 13:45:26" }, { "name": "zendframework/zend-captcha", @@ -2016,7 +2016,7 @@ "captcha", "zf2" ], - "time": "2017-02-23T08:09:44+00:00" + "time": "2017-02-23 08:09:44" }, { "name": "zendframework/zend-code", @@ -2069,7 +2069,7 @@ "code", "zf2" ], - "time": "2016-10-24T13:23:32+00:00" + "time": "2016-10-24 13:23:32" }, { "name": "zendframework/zend-config", @@ -2125,7 +2125,7 @@ "config", "zf2" ], - "time": "2016-02-04T23:01:10+00:00" + "time": "2016-02-04 23:01:10" }, { "name": "zendframework/zend-console", @@ -2177,7 +2177,7 @@ "console", "zf2" ], - "time": "2016-02-09T17:15:12+00:00" + "time": "2016-02-09 17:15:12" }, { "name": "zendframework/zend-crypt", @@ -2227,7 +2227,7 @@ "crypt", "zf2" ], - "time": "2016-02-03T23:46:30+00:00" + "time": "2016-02-03 23:46:30" }, { "name": "zendframework/zend-db", @@ -2284,7 +2284,7 @@ "db", "zf2" ], - "time": "2016-08-09T19:28:55+00:00" + "time": "2016-08-09 19:28:55" }, { "name": "zendframework/zend-di", @@ -2331,7 +2331,7 @@ "di", "zf2" ], - "time": "2016-04-25T20:58:11+00:00" + "time": "2016-04-25 20:58:11" }, { "name": "zendframework/zend-escaper", @@ -2375,7 +2375,7 @@ "escaper", "zf2" ], - "time": "2016-06-30T19:48:38+00:00" + "time": "2016-06-30 19:48:38" }, { "name": "zendframework/zend-eventmanager", @@ -2422,7 +2422,7 @@ "eventmanager", "zf2" ], - "time": "2016-02-18T20:49:05+00:00" + "time": "2016-02-18 20:49:05" }, { "name": "zendframework/zend-filter", @@ -2482,7 +2482,7 @@ "filter", "zf2" ], - "time": "2017-05-17T20:56:17+00:00" + "time": "2017-05-17 20:56:17" }, { "name": "zendframework/zend-form", @@ -2559,39 +2559,39 @@ "form", "zf2" ], - "time": "2017-05-18T14:59:53+00:00" + "time": "2017-05-18 14:59:53" }, { "name": "zendframework/zend-http", - "version": "2.6.0", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "09f4d279f46d86be63171ff62ee0f79eca878678" + "reference": "78aa510c0ea64bfb2aa234f50c4f232c9531acfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/09f4d279f46d86be63171ff62ee0f79eca878678", - "reference": "09f4d279f46d86be63171ff62ee0f79eca878678", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/78aa510c0ea64bfb2aa234f50c4f232c9531acfa", + "reference": "78aa510c0ea64bfb2aa234f50c4f232c9531acfa", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.5 || ^3.0", - "zendframework/zend-uri": "^2.5", - "zendframework/zend-validator": "^2.5" + "php": "^5.6 || ^7.0", + "zendframework/zend-loader": "^2.5.1", + "zendframework/zend-stdlib": "^3.1 || ^2.7.7", + "zendframework/zend-uri": "^2.5.2", + "zendframework/zend-validator": "^2.10.1" }, "require-dev": { - "phpunit/phpunit": "^4.0", + "phpunit/phpunit": "^6.4.1 || ^5.7.15", "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.5" + "zendframework/zend-config": "^3.1 || ^2.6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "dev-master": "2.7-dev", + "dev-develop": "2.8-dev" } }, "autoload": { @@ -2606,10 +2606,13 @@ "description": "provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", "homepage": "https://github.com/zendframework/zend-http", "keywords": [ + "ZendFramework", "http", - "zf2" + "http client", + "zend", + "zf" ], - "time": "2017-01-31T14:41:02+00:00" + "time": "2017-10-13 12:06:24" }, { "name": "zendframework/zend-hydrator", @@ -2667,7 +2670,7 @@ "hydrator", "zf2" ], - "time": "2016-02-18T22:38:26+00:00" + "time": "2016-02-18 22:38:26" }, { "name": "zendframework/zend-i18n", @@ -2734,7 +2737,7 @@ "i18n", "zf2" ], - "time": "2017-05-17T17:00:12+00:00" + "time": "2017-05-17 17:00:12" }, { "name": "zendframework/zend-inputfilter", @@ -2789,7 +2792,7 @@ "inputfilter", "zf2" ], - "time": "2017-05-18T14:20:56+00:00" + "time": "2017-05-18 14:20:56" }, { "name": "zendframework/zend-json", @@ -2844,7 +2847,7 @@ "json", "zf2" ], - "time": "2016-02-04T21:20:26+00:00" + "time": "2016-02-04 21:20:26" }, { "name": "zendframework/zend-loader", @@ -2888,7 +2891,7 @@ "loader", "zf2" ], - "time": "2015-06-03T14:05:47+00:00" + "time": "2015-06-03 14:05:47" }, { "name": "zendframework/zend-log", @@ -2959,7 +2962,7 @@ "logging", "zf2" ], - "time": "2017-05-17T16:03:26+00:00" + "time": "2017-05-17 16:03:26" }, { "name": "zendframework/zend-math", @@ -3009,20 +3012,20 @@ "math", "zf2" ], - "time": "2016-04-07T16:29:53+00:00" + "time": "2016-04-07 16:29:53" }, { "name": "zendframework/zend-modulemanager", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-modulemanager.git", - "reference": "c2c5b52ad9741e0b9a9c01a0ee72ab63e5b494b9" + "reference": "710c13353b1ff0975777dbeb39bbf1c85e3353a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/c2c5b52ad9741e0b9a9c01a0ee72ab63e5b494b9", - "reference": "c2c5b52ad9741e0b9a9c01a0ee72ab63e5b494b9", + "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/710c13353b1ff0975777dbeb39bbf1c85e3353a3", + "reference": "710c13353b1ff0975777dbeb39bbf1c85e3353a3", "shasum": "" }, "require": { @@ -3067,7 +3070,7 @@ "modulemanager", "zf2" ], - "time": "2017-07-11T19:39:57+00:00" + "time": "2017-11-01 18:30:41" }, { "name": "zendframework/zend-mvc", @@ -3154,7 +3157,7 @@ "mvc", "zf2" ], - "time": "2016-02-23T15:24:59+00:00" + "time": "2016-02-23 15:24:59" }, { "name": "zendframework/zend-serializer", @@ -3211,7 +3214,7 @@ "serializer", "zf2" ], - "time": "2016-06-21T17:01:55+00:00" + "time": "2016-06-21 17:01:55" }, { "name": "zendframework/zend-server", @@ -3257,7 +3260,7 @@ "server", "zf2" ], - "time": "2016-06-20T22:27:55+00:00" + "time": "2016-06-20 22:27:55" }, { "name": "zendframework/zend-servicemanager", @@ -3309,7 +3312,7 @@ "servicemanager", "zf2" ], - "time": "2016-12-19T19:14:29+00:00" + "time": "2016-12-19 19:14:29" }, { "name": "zendframework/zend-session", @@ -3375,7 +3378,7 @@ "session", "zf2" ], - "time": "2017-06-19T21:31:39+00:00" + "time": "2017-06-19 21:31:39" }, { "name": "zendframework/zend-soap", @@ -3427,7 +3430,7 @@ "soap", "zf2" ], - "time": "2016-04-21T16:06:27+00:00" + "time": "2016-04-21 16:06:27" }, { "name": "zendframework/zend-stdlib", @@ -3486,7 +3489,7 @@ "stdlib", "zf2" ], - "time": "2016-04-12T21:17:31+00:00" + "time": "2016-04-12 21:17:31" }, { "name": "zendframework/zend-text", @@ -3533,7 +3536,7 @@ "text", "zf2" ], - "time": "2016-02-08T19:03:52+00:00" + "time": "2016-02-08 19:03:52" }, { "name": "zendframework/zend-uri", @@ -3580,7 +3583,7 @@ "uri", "zf2" ], - "time": "2016-02-17T22:38:51+00:00" + "time": "2016-02-17 22:38:51" }, { "name": "zendframework/zend-validator", @@ -3651,7 +3654,7 @@ "validator", "zf2" ], - "time": "2017-08-22T14:19:23+00:00" + "time": "2017-08-22 14:19:23" }, { "name": "zendframework/zend-view", @@ -3738,7 +3741,7 @@ "view", "zf2" ], - "time": "2017-03-21T15:05:56+00:00" + "time": "2017-03-21 15:05:56" } ], "packages-dev": [ @@ -3794,7 +3797,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2015-06-14 21:17:01" }, { "name": "friendsofphp/php-cs-fixer", @@ -3864,7 +3867,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-03-31T12:59:38+00:00" + "time": "2017-03-31 12:59:38" }, { "name": "ircmaxell/password-compat", @@ -3906,7 +3909,7 @@ "hashing", "password" ], - "time": "2014-11-20T16:49:30+00:00" + "time": "2014-11-20 16:49:30" }, { "name": "lusitanian/oauth", @@ -3973,41 +3976,44 @@ "oauth", "security" ], - "time": "2016-07-12T22:15:40+00:00" + "time": "2016-07-12 22:15:40" }, { "name": "myclabs/deep-copy", - "version": "1.6.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", - "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" - } + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -4015,7 +4021,7 @@ "object", "object graph" ], - "time": "2017-04-12T18:52:22+00:00" + "time": "2017-10-19 19:58:43" }, { "name": "pdepend/pdepend", @@ -4055,7 +4061,7 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-01-19T14:23:36+00:00" + "time": "2017-01-19 14:23:36" }, { "name": "phar-io/manifest", @@ -4110,7 +4116,7 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "time": "2017-03-05 18:14:27" }, { "name": "phar-io/version", @@ -4157,7 +4163,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2017-03-05 17:38:23" }, { "name": "phpdocumentor/reflection-common", @@ -4211,7 +4217,7 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2017-09-11 18:02:19" }, { "name": "phpdocumentor/reflection-docblock", @@ -4256,7 +4262,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-08-30T18:51:59+00:00" + "time": "2017-08-30 18:51:59" }, { "name": "phpdocumentor/type-resolver", @@ -4303,7 +4309,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "time": "2017-07-14 14:27:02" }, { "name": "phpmd/phpmd", @@ -4369,7 +4375,7 @@ "phpmd", "pmd" ], - "time": "2017-01-20T14:41:10+00:00" + "time": "2017-01-20 14:41:10" }, { "name": "phpspec/prophecy", @@ -4432,20 +4438,20 @@ "spy", "stub" ], - "time": "2017-09-04T11:05:03+00:00" + "time": "2017-09-04 11:05:03" }, { "name": "phpunit/php-code-coverage", - "version": "5.2.2", + "version": "5.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "8ed1902a57849e117b5651fc1a5c48110946c06b" + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8ed1902a57849e117b5651fc1a5c48110946c06b", - "reference": "8ed1902a57849e117b5651fc1a5c48110946c06b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", "shasum": "" }, "require": { @@ -4454,7 +4460,7 @@ "php": "^7.0", "phpunit/php-file-iterator": "^1.4.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^1.4.11 || ^2.0", + "phpunit/php-token-stream": "^2.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^3.0", "sebastian/version": "^2.0.1", @@ -4496,7 +4502,7 @@ "testing", "xunit" ], - "time": "2017-08-03T12:40:43+00:00" + "time": "2017-11-03 13:47:33" }, { "name": "phpunit/php-file-iterator", @@ -4543,7 +4549,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03T07:40:28+00:00" + "time": "2016-10-03 07:40:28" }, { "name": "phpunit/php-text-template", @@ -4584,7 +4590,7 @@ "keywords": [ "template" ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2015-06-21 13:50:34" }, { "name": "phpunit/php-timer", @@ -4633,7 +4639,7 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2017-02-26 11:10:40" }, { "name": "phpunit/php-token-stream", @@ -4682,7 +4688,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-08-20T05:47:52+00:00" + "time": "2017-08-20 05:47:52" }, { "name": "phpunit/phpunit", @@ -4766,7 +4772,7 @@ "testing", "xunit" ], - "time": "2017-08-03T13:59:28+00:00" + "time": "2017-08-03 13:59:28" }, { "name": "phpunit/phpunit-mock-objects", @@ -4825,7 +4831,7 @@ "mock", "xunit" ], - "time": "2017-08-03T14:08:16+00:00" + "time": "2017-08-03 14:08:16" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -4870,7 +4876,7 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "time": "2017-03-04 06:30:41" }, { "name": "sebastian/comparator", @@ -4934,7 +4940,7 @@ "compare", "equality" ], - "time": "2017-03-03T06:26:08+00:00" + "time": "2017-03-03 06:26:08" }, { "name": "sebastian/diff", @@ -4986,7 +4992,7 @@ "keywords": [ "diff" ], - "time": "2017-05-22T07:24:03+00:00" + "time": "2017-05-22 07:24:03" }, { "name": "sebastian/environment", @@ -5036,7 +5042,7 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2017-07-01 08:51:00" }, { "name": "sebastian/exporter", @@ -5103,7 +5109,7 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2017-04-03 13:19:02" }, { "name": "sebastian/finder-facade", @@ -5142,7 +5148,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2016-02-17T07:02:23+00:00" + "time": "2016-02-17 07:02:23" }, { "name": "sebastian/global-state", @@ -5193,7 +5199,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2017-04-27 15:39:26" }, { "name": "sebastian/object-enumerator", @@ -5240,7 +5246,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "time": "2017-08-03 12:35:26" }, { "name": "sebastian/object-reflector", @@ -5285,7 +5291,7 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "time": "2017-03-29 09:07:27" }, { "name": "sebastian/phpcpd", @@ -5336,7 +5342,7 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2016-04-17T19:32:49+00:00" + "time": "2016-04-17 19:32:49" }, { "name": "sebastian/recursion-context", @@ -5389,7 +5395,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "time": "2017-03-03 06:23:57" }, { "name": "sebastian/resource-operations", @@ -5431,7 +5437,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2015-07-28 20:34:47" }, { "name": "sebastian/version", @@ -5474,7 +5480,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "time": "2016-10-03 07:35:21" }, { "name": "squizlabs/php_codesniffer", @@ -5525,20 +5531,20 @@ "phpcs", "standards" ], - "time": "2017-06-14T01:23:49+00:00" + "time": "2017-06-14 01:23:49" }, { "name": "symfony/config", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f9f19a39ee178f61bb2190f51ff7c517c2159315" + "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f9f19a39ee178f61bb2190f51ff7c517c2159315", - "reference": "f9f19a39ee178f61bb2190f51ff7c517c2159315", + "url": "https://api.github.com/repos/symfony/config/zipball/4ab62407bff9cd97c410a7feaef04c375aaa5cfd", + "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd", "shasum": "" }, "require": { @@ -5587,20 +5593,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-09-04T16:28:07+00:00" + "time": "2017-10-04 18:56:58" }, { "name": "symfony/dependency-injection", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e593f06dd90a81c7b70ac1c49862a061b0ec06d2" + "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e593f06dd90a81c7b70ac1c49862a061b0ec06d2", - "reference": "e593f06dd90a81c7b70ac1c49862a061b0ec06d2", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8ebad929aee3ca185b05f55d9cc5521670821ad1", + "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1", "shasum": "" }, "require": { @@ -5657,20 +5663,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-09-05T20:39:38+00:00" + "time": "2017-10-04 17:15:30" }, { "name": "symfony/polyfill-php54", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "b7763422a5334c914ef0298ed21b253d25913a6e" + "reference": "d7810a14b2c6c1aff415e1bb755f611b3d5327bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/b7763422a5334c914ef0298ed21b253d25913a6e", - "reference": "b7763422a5334c914ef0298ed21b253d25913a6e", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/d7810a14b2c6c1aff415e1bb755f611b3d5327bc", + "reference": "d7810a14b2c6c1aff415e1bb755f611b3d5327bc", "shasum": "" }, "require": { @@ -5679,7 +5685,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5715,20 +5721,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-php55", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "29b1381d66f16e0581aab0b9f678ccf073288f68" + "reference": "b64e7f0c37ecf144ecc16668936eef94e628fbfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/29b1381d66f16e0581aab0b9f678ccf073288f68", - "reference": "29b1381d66f16e0581aab0b9f678ccf073288f68", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/b64e7f0c37ecf144ecc16668936eef94e628fbfd", + "reference": "b64e7f0c37ecf144ecc16668936eef94e628fbfd", "shasum": "" }, "require": { @@ -5738,7 +5744,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5771,20 +5777,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-php70", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "b6482e68974486984f59449ecea1fbbb22ff840f" + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/b6482e68974486984f59449ecea1fbbb22ff840f", - "reference": "b6482e68974486984f59449ecea1fbbb22ff840f", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", "shasum": "" }, "require": { @@ -5794,7 +5800,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5830,20 +5836,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-php72", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "8abc9097f5001d310f0edba727469c988acc6ea7" + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8abc9097f5001d310f0edba727469c988acc6ea7", - "reference": "8abc9097f5001d310f0edba727469c988acc6ea7", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/6de4f4884b97abbbed9f0a84a95ff2ff77254254", + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254", "shasum": "" }, "require": { @@ -5852,7 +5858,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -5885,20 +5891,20 @@ "portable", "shim" ], - "time": "2017-07-11T13:25:55+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/polyfill-xml", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-xml.git", - "reference": "7d536462e554da7b05600a926303bf9b99153275" + "reference": "d7bcb5c3bb1832c532379df50825c08f43a64134" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/7d536462e554da7b05600a926303bf9b99153275", - "reference": "7d536462e554da7b05600a926303bf9b99153275", + "url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/d7bcb5c3bb1832c532379df50825c08f43a64134", + "reference": "d7bcb5c3bb1832c532379df50825c08f43a64134", "shasum": "" }, "require": { @@ -5908,7 +5914,7 @@ "type": "metapackage", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -5933,20 +5939,20 @@ "portable", "shim" ], - "time": "2017-06-14T15:44:48+00:00" + "time": "2017-10-11 12:05:26" }, { "name": "symfony/stopwatch", - "version": "v3.3.9", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb" + "reference": "170edf8b3247d7b6779eb6fa7428f342702ca184" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/9a5610a8d6a50985a7be485c0ba745c22607beeb", - "reference": "9a5610a8d6a50985a7be485c0ba745c22607beeb", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/170edf8b3247d7b6779eb6fa7428f342702ca184", + "reference": "170edf8b3247d7b6779eb6fa7428f342702ca184", "shasum": "" }, "require": { @@ -5982,7 +5988,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-07-29T21:54:42+00:00" + "time": "2017-10-02 06:42:24" }, { "name": "theseer/fdomdocument", @@ -6022,7 +6028,7 @@ ], "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2017-06-30T11:53:12+00:00" + "time": "2017-06-30 11:53:12" }, { "name": "theseer/tokenizer", @@ -6062,7 +6068,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2017-04-07 12:08:54" }, { "name": "webmozart/assert", @@ -6112,7 +6118,7 @@ "check", "validate" ], - "time": "2016-11-23T20:04:58+00:00" + "time": "2016-11-23 20:04:58" } ], "aliases": [], diff --git a/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php b/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6fd7551676660c8663d6c872f8ae4d15cc7e4e82 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Api; + +use Magento\Analytics\Model\FileInfoManager; +use Magento\Framework\UrlInterface; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Class LinkProviderTest. + * + * Checks that api for providing link to encrypted archive works. + */ +class LinkProviderTest extends WebapiAbstract +{ + const SERVICE_VERSION = 'V1'; + const SERVICE_NAME = 'analyticsLinkProviderV1'; + const RESOURCE_PATH = '/V1/analytics/link'; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Analytics/_files/create_link.php + */ + public function testGetAll() + { + $objectManager = Bootstrap::getObjectManager(); + + /** + * @var $fileInfoManager FileInfoManager + */ + $fileInfoManager = $objectManager->create(FileInfoManager::class); + + $storeManager = $objectManager->create(StoreManagerInterface::class); + + $fileInfo = $fileInfoManager->load(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => static::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => static::SERVICE_NAME, + 'serviceVersion' => static::SERVICE_VERSION, + 'operation' => static::SERVICE_NAME . 'Get', + ], + ]; + if (!$this->isTestBaseUrlSecure()) { + try { + $this->_webApiCall($serviceInfo); + } catch (\Exception $e) { + $this->assertContains( + 'Operation allowed only in HTTPS', + $e->getMessage() + ); + return; + } + $this->fail("Exception 'Operation allowed only in HTTPS' should be thrown"); + } else { + $response = $this->_webApiCall($serviceInfo); + $this->assertEquals(2, count($response)); + $this->assertEquals( + base64_encode($fileInfo->getInitializationVector()), + $response['initialization_vector'] + ); + $this->assertEquals( + $storeManager->getStore()->getBaseUrl( + UrlInterface::URL_TYPE_MEDIA + ) . $fileInfo->getPath(), + $response['url'] + ); + } + } + + /** + * @return bool + */ + private function isTestBaseUrlSecure() + { + return strpos('https://', TESTS_BASE_URL) !== false; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/Adminhtml/Dashboard/AdvancedReporting/ReportsSectionBlock.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/Adminhtml/Dashboard/AdvancedReporting/ReportsSectionBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..1c7edaaac86f0da798a9a01ba6924223d02994e8 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/Adminhtml/Dashboard/AdvancedReporting/ReportsSectionBlock.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Block\Adminhtml\Dashboard\AdvancedReporting; + +use Magento\Mtf\Block\Block; + +/** + * Advanced Reporting section on dashboard. + */ +class ReportsSectionBlock extends Block +{ + /** + * Advanced Reporting button on dashboard. + * + * @var string + */ + protected $advancedReportingButton = '[data-index="analytics-service-link"]'; + + /** + * Click Advanced Reporting link. + * + * @return void + */ + public function click() + { + $this->_rootElement->find($this->advancedReportingButton)->click(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php new file mode 100644 index 0000000000000000000000000000000000000000..07b62a9518ae4d6feef8064ea68de5652596e5ac --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php @@ -0,0 +1,158 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Block\System\Config; + +use Magento\Mtf\Block\Form; +use Magento\Mtf\Client\Locator; + +/** + * Analytics form in admin configurations. + * + */ +class AnalyticsForm extends Form +{ + /** + * @var string + */ + private $analyticsStatus = '#analytics_general_enabled'; + + /** + * @var string + */ + private $analyticsStatusLabel = '#row_analytics_general_enabled > td.value > p > span'; + + /** + * @var string + */ + private $submitButton = '#save'; + + /** + * @var string + */ + private $analyticsVertical = '#analytics_general_vertical'; + + /** + * @var string + */ + private $analyticsVerticalScope = '#row_analytics_general_vertical span[data-config-scope="[WEBSITE]"]'; + + /** + * @var string + */ + private $sendDataTimeHh = '#row_analytics_general_collection_time > td.value > select:nth-child(2)'; + + /** + * @var string + */ + private $sendDataTimeMm = '#row_analytics_general_collection_time > td.value > select:nth-child(3)'; + + /** + * @var string + */ + private $sendDataTimeSs = '#row_analytics_general_collection_time > td.value > select:nth-child(4)'; + + /** + * @var string + */ + private $timeZone = + '#row_analytics_general_collection_time > td.value > p > span'; + + /** + * @return array|string + */ + public function isAnalyticsEnabled() + { + return $this->_rootElement->find($this->analyticsStatus, Locator::SELECTOR_CSS)->getValue(); + } + + /** + * @param string $state + * @return array|string + */ + public function analyticsToggle($state = 'Enable') + { + return $this->_rootElement->find($this->analyticsStatus, Locator::SELECTOR_CSS, 'select')->setValue($state); + } + + /** + * @return array|string + */ + public function saveConfig() + { + return $this->browser->find($this->submitButton)->click(); + } + + /** + * @return array|string + */ + public function getAnalyticsStatus() + { + return $this->_rootElement->find($this->analyticsStatusLabel, Locator::SELECTOR_CSS)->getText(); + } + + /** + * @param string $vertical + * @return array|string + */ + public function setAnalyticsVertical($vertical) + { + return $this->_rootElement->find($this->analyticsVertical, Locator::SELECTOR_CSS, 'select') + ->setValue($vertical); + } + + /** + * @param string $hh + * @param string $mm + * @return $this + */ + public function setTimeOfDayToSendData($hh, $mm) + { + $this->_rootElement->find($this->sendDataTimeHh, Locator::SELECTOR_CSS, 'select') + ->setValue($hh); + $this->_rootElement->find($this->sendDataTimeMm, Locator::SELECTOR_CSS, 'select') + ->setValue($mm); + return $this; + } + + /** + * @return string + */ + public function getTimeOfDayToSendDate() + { + $hh = $this->_rootElement->find($this->sendDataTimeHh, Locator::SELECTOR_CSS, 'select') + ->getValue(); + $mm = $this->_rootElement->find($this->sendDataTimeMm, Locator::SELECTOR_CSS, 'select') + ->getValue(); + $ss = $this->_rootElement->find($this->sendDataTimeSs, Locator::SELECTOR_CSS, 'select') + ->getValue(); + return sprintf('%s, %s, %s', $hh, $mm, $ss); + } + + /** + * @return mixed + */ + public function getTimeZone() + { + return $this->_rootElement->find($this->timeZone, Locator::SELECTOR_CSS) + ->getText(); + } + + /** + * @return array|string + */ + public function getAnalyticsVertical() + { + return $this->_rootElement->find($this->analyticsVertical, Locator::SELECTOR_CSS)->getValue(); + } + + /** + * @return array|string + */ + public function getAnalyticsVerticalScope() + { + return $this->_rootElement->find($this->analyticsVerticalScope, Locator::SELECTOR_CSS)->isVisible(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingPage.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingPage.php new file mode 100644 index 0000000000000000000000000000000000000000..de379aea85fa7993206718ed5635a772aa9bf814 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingPage.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Client\BrowserInterface; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert Advanced Reporting Sign Up page is opened by admin dashboard link. + */ +class AssertAdvancedReportingPage extends AbstractConstraint +{ + /** + * Browser instance. + * + * @var BrowserInterface + */ + private $browser; + + /** + * Assert Advanced Reporting Sign Up page is opened by link. + * + * @param BrowserInterface $browser + * @param string $advancedReportingLink + * @return void + */ + public function processAssert(BrowserInterface $browser, $advancedReportingLink) + { + $this->browser = $browser; + $this->browser->selectWindow(); + \PHPUnit_Framework_Assert::assertTrue( + $this->browser->waitUntil( + function () use ($advancedReportingLink) { + return ($this->browser->getUrl() === $advancedReportingLink) ? true : null; + } + ), + 'Advanced Reporting Sign Up page was not opened by link.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Advanced Reporting Sign Up page is opened by link'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingSectionInvisible.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingSectionInvisible.php new file mode 100644 index 0000000000000000000000000000000000000000..d154ee275710a40b591f9e6201cf2ee16303d29b --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingSectionInvisible.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Backend\Test\Page\Adminhtml\SystemConfigEdit; + +/** + * Assert that Advanced Reporting section is invisibile. + */ +class AssertAdvancedReportingSectionInvisible extends AbstractConstraint +{ + /** + * Assert Advanced Reporting section is invisibile. + * + * @param SystemConfigEdit $configEdit + * @return void + */ + public function processAssert(SystemConfigEdit $configEdit) + { + $configEdit->open(); + \PHPUnit_Framework_Assert::assertFalse( + in_array('Advanced Reporting', $configEdit->getTabs()->getSubTabsNames('General')), + 'Advanced Reporting section is visible.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Advanced Reporting section is invisible.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingSectionVisible.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingSectionVisible.php new file mode 100644 index 0000000000000000000000000000000000000000..18ea93fee1ea368234559e9994108296f4b35045 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertAdvancedReportingSectionVisible.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Backend\Test\Page\Adminhtml\SystemConfigEdit; + +/** + * Assert that Advanced Reporting section is visibile. + */ +class AssertAdvancedReportingSectionVisible extends AbstractConstraint +{ + /** + * Assert Advanced Reporting section is visibile. + * + * @param SystemConfigEdit $configEdit + * @return void + */ + public function processAssert(SystemConfigEdit $configEdit) + { + $configEdit->open(); + \PHPUnit_Framework_Assert::assertTrue( + in_array('Advanced Reporting', $configEdit->getTabs()->getSubTabsNames('General')), + 'Advanced Reporting section is not visible.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Advanced Reporting section is visible.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php new file mode 100644 index 0000000000000000000000000000000000000000..010d9c446819da12a0077167c6a95a5936cdb685 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Client\BrowserInterface; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Backend\Test\Page\Adminhtml\Dashboard; + +/** + * Assert BI Essentials Sign Up page is opened by admin menu link + */ +class AssertBIEssentialsLink extends AbstractConstraint +{ + /** + * Count of try for choose menu item. + */ + const MAX_TRY_COUNT = 2; + + /** + * Browser instance. + * + * @var BrowserInterface + */ + protected $browser; + + /** + * Assert BI Essentials Sign Up page is opened by link + * + * @param BrowserInterface $browser + * @param string $businessIntelligenceLink + * @param Dashboard $dashboard + * @param string $menuItem + * @param bool $waitMenuItemNotVisible + * @return void + */ + public function processAssert( + BrowserInterface $browser, + $businessIntelligenceLink, + Dashboard $dashboard, + $menuItem, + $waitMenuItemNotVisible = false + ) { + /** + * In the parallel run case new windows that adding to selenium grid windows handler + * are in competition with another windows in another browsers in the same selenium grid. + * During this case need to have some algorithm for retrying some operations that changed + * current window for browser, because it's some times happens. + */ + $this->browser = $browser; + $count = 0; + $isVisible = false; + do { + try { + $this->browser->selectWindow(); + $isVisible = $this->browser->waitUntil(function () use ($businessIntelligenceLink) { + return ($this->browser->getUrl() === $businessIntelligenceLink) ?: null; + }); + break; + } catch (\Throwable $e) { + $dashboard->open(); + $dashboard->getMenuBlock()->navigate($menuItem, $waitMenuItemNotVisible); + $count++; + } + } while ($count < self::MAX_TRY_COUNT); + + \PHPUnit_Framework_Assert::assertTrue( + $isVisible, + "BI Essentials Sign Up page was not opened by link.\n + Actual link is '{$this->browser->getUrl()}'\n + Expected link is '$businessIntelligenceLink'" + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'BI Essentials Sign Up page is opened by link'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php new file mode 100644 index 0000000000000000000000000000000000000000..0f65835a32aa77f67fbc1d42d099647f8f032cc0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; +use Magento\Analytics\Test\TestStep\OpenAnalyticsConfigStep; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert Advanced Reporting service is disabled + */ +class AssertConfigAnalyticsDisabled extends AbstractConstraint +{ + /** + * Assert Advanced Reporting service is disabled. + * + * @param ConfigAnalytics $configAnalytics + * @param OpenAnalyticsConfigStep $openAnalyticsConfigStep + * @return void + */ + public function processAssert(ConfigAnalytics $configAnalytics, OpenAnalyticsConfigStep $openAnalyticsConfigStep) + { + $openAnalyticsConfigStep->run(); + + \PHPUnit_Framework_Assert::assertFalse( + (bool)$configAnalytics->getAnalyticsForm()->isAnalyticsEnabled(), + 'Magento Advanced Reporting service is not disabled.' + ); + \PHPUnit_Framework_Assert::assertEquals( + $configAnalytics->getAnalyticsForm()->getAnalyticsStatus(), + 'Subscription status: Disabled', + 'Magento Advanced Reporting service subscription status is not disabled.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Magento Advanced Reporting service is disabled and has Disabled status.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php new file mode 100644 index 0000000000000000000000000000000000000000..8fd04e06b14bb5100e766f1ad3478cb32c40e580 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; +use Magento\Analytics\Test\TestStep\OpenAnalyticsConfigStep; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert Advanced Reporting Service is enabled. + */ +class AssertConfigAnalyticsEnabled extends AbstractConstraint +{ + /** + * Assert Advanced Reporting service is enabled. + * + * @param ConfigAnalytics $configAnalytics + * @param OpenAnalyticsConfigStep $openAnalyticsConfigStep + * @return void + */ + public function processAssert(ConfigAnalytics $configAnalytics, OpenAnalyticsConfigStep $openAnalyticsConfigStep) + { + $openAnalyticsConfigStep->run(); + + \PHPUnit_Framework_Assert::assertTrue( + (bool)$configAnalytics->getAnalyticsForm()->isAnalyticsEnabled(), + 'Magento Advanced Reporting service is not enabled.' + ); + + \PHPUnit_Framework_Assert::assertEquals( + $configAnalytics->getAnalyticsForm()->getAnalyticsStatus(), + 'Subscription status: Pending', + 'Magento Advanced Reporting service subscription status is not pending.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Magento Advanced Reporting service is enabled and has Pending status'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsIndustryScope.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsIndustryScope.php new file mode 100644 index 0000000000000000000000000000000000000000..bb208dbb379c8faa9a3d7bc540810f68236df0e2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsIndustryScope.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; + +/** + * Assert Advanced Reporting industry scope is website in Stores. + */ +class AssertConfigAnalyticsIndustryScope extends AbstractConstraint +{ + /** + * Assert Advanced Reporting industry scope is website in Stores. + * + * @param ConfigAnalytics $configAnalytics + */ + public function processAssert(ConfigAnalytics $configAnalytics) + { + \PHPUnit_Framework_Assert::assertEquals( + true, + $configAnalytics->getAnalyticsForm()->getAnalyticsVerticalScope(), + 'Magento Advanced Reporting industry scope is not website' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Magento Advanced Reporting industry scope is website'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsSendingTimeAndZone.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsSendingTimeAndZone.php new file mode 100644 index 0000000000000000000000000000000000000000..1de9405e61f2155d3b5cfc53470b555ee18b95e2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsSendingTimeAndZone.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; +use Magento\Analytics\Test\TestStep\OpenAnalyticsConfigStep; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert sending data to the Analytics is restored. + */ +class AssertConfigAnalyticsSendingTimeAndZone extends AbstractConstraint +{ + /** + * @param ConfigAnalytics $configAnalytics + * @param OpenAnalyticsConfigStep $openAnalyticsConfigStep + * @param string $hh + * @param string $mm + * @return void + */ + public function processAssert( + ConfigAnalytics $configAnalytics, + OpenAnalyticsConfigStep $openAnalyticsConfigStep, + $hh, + $mm + ) { + $openAnalyticsConfigStep->run(); + + \PHPUnit_Framework_Assert::assertEquals( + 'Eastern European Standard Time (Europe/Kiev)', + $configAnalytics->getAnalyticsForm()->getTimeZone() + ); + + \PHPUnit_Framework_Assert::assertEquals( + sprintf('%s, %s, 00', $hh, $mm), + $configAnalytics->getAnalyticsForm()->getTimeOfDayToSendDate() + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Time and TimeZone are correct!'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertEmptyIndustryCanNotBeSaved.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertEmptyIndustryCanNotBeSaved.php new file mode 100644 index 0000000000000000000000000000000000000000..5e86c13b8bbaec1a2ea56c6cf3ce0f3f0370b4b1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertEmptyIndustryCanNotBeSaved.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; + +/** + * Assert empty industry can not be saved in advanced reporting configuration. + */ +class AssertEmptyIndustryCanNotBeSaved extends AbstractConstraint +{ + /** + * Assert empty industry can not be saved in Advanced Reporting configuration. + * + * @param ConfigAnalytics $configAnalytics + * @param string $errorMessage + * @return void + */ + public function processAssert(ConfigAnalytics $configAnalytics, $errorMessage) + { + \PHPUnit_Framework_Assert::assertEquals( + $errorMessage, + $configAnalytics->getMessages()->getErrorMessage(), + 'There is no error message when saving empty industry in configuration' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return + 'Empty Magento Advanced Reporting industry can not be saved'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertIndustryIsSet.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertIndustryIsSet.php new file mode 100644 index 0000000000000000000000000000000000000000..635c4b35324ed69303d253ddbf2e647518b5053a --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertIndustryIsSet.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\Constraint; + +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; + +/** + * Assert Advance Reporting Industry is set. + */ +class AssertIndustryIsSet extends AbstractConstraint +{ + /** + * Assert Advance Reporting Industry is set + * + * @param ConfigAnalytics $configAnalytics + * @param string $industry + * @return void + */ + public function processAssert(ConfigAnalytics $configAnalytics, $industry) + { + \PHPUnit_Framework_Assert::assertEquals( + $industry, + $configAnalytics->getAnalyticsForm()->getAnalyticsVertical(), + $industry . 'industry is not selected' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return + 'Proper Magento Advanced Reporting industry is selected'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Page/Adminhtml/ConfigAnalytics.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/Page/Adminhtml/ConfigAnalytics.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4a96e588261fe73207953350147b4d01e29bb2f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Page/Adminhtml/ConfigAnalytics.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> + <page name="ConfigAnalytics" area="Adminhtml" mca="admin/system_config/edit/section/analytics" module="Magento_Analytics"> + <block name="AnalyticsForm" class="Magento\Analytics\Test\Block\System\Config\AnalyticsForm" locator="[id='page:main-container']" strategy="css selector" /> + <block name="Messages" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector" /> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Page/Adminhtml/Dashboard.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/Page/Adminhtml/Dashboard.xml new file mode 100644 index 0000000000000000000000000000000000000000..8c8e75c03d24dda0786c48f9f7f95c3d0511e14c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Page/Adminhtml/Dashboard.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> + <page name="Dashboard" area="Adminhtml" mca="admin/dashboard" module="Magento_Backend"> + <block name="reportsSectionBlock" class="Magento\Analytics\Test\Block\Adminhtml\Dashboard\AdvancedReporting\ReportsSectionBlock" locator="[data-index='dashboard-advanced-reports']" strategy="css selector" /> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/DefaultTimeZone.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/DefaultTimeZone.xml new file mode 100644 index 0000000000000000000000000000000000000000..80d142f8abd608603ac7d0ec2c70aa911f9f53b7 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/DefaultTimeZone.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/Repository/etc/repository.xsd"> + <repository class="Magento\Config\Test\Repository\ConfigData"> + <dataset name="change_default_timezone"> + <field name="general/locale/timezone" xsi:type="array"> + <item name="scope_id" xsi:type="number">0</item> + <item name="label" xsi:type="string">Timezone</item> + <item name="value" xsi:type="string">Europe/Kiev</item> + </field> + <field name="analytics/general/collection_time" xsi:type="array"> + <item name="scope_id" xsi:type="number">0</item> + <item name="label" xsi:type="string">Time of day to send data</item> + <item name="value" xsi:type="string">01,00,00</item> + </field> + </dataset> + <dataset name="change_default_timezone_rollback"> + <field name="general/locale/timezone" xsi:type="array"> + <item name="scope_id" xsi:type="number">0</item> + <item name="label" xsi:type="string">Timezone</item> + <item name="value" xsi:type="string">UTC</item> + </field> + <field name="analytics/general/collection_time" xsi:type="array"> + <item name="scope_id" xsi:type="number">0</item> + <item name="label" xsi:type="string">Time of day to send data</item> + <item name="value" xsi:type="string">02,00,00</item> + </field> + </dataset> + </repository> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/Integration.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/Integration.xml new file mode 100644 index 0000000000000000000000000000000000000000..0b4f80512f1979101e3621d0bacc5986b611be66 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/Integration.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/Repository/etc/repository.xsd"> + <repository class="Magento\Integration\Test\Repository\Integration"> + <dataset name="default_with_all_resources"> + <field name="resources" xsi:type="array"> + <item name="100" xsi:type="string">Analytics</item> + </field> + </dataset> + </repository> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/Role.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/Role.xml new file mode 100644 index 0000000000000000000000000000000000000000..77cc8b5fac0381c56c145bfb43f61c03df06a205 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/Role.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/Repository/etc/repository.xsd"> + <repository class="Magento\User\Test\Repository\Role"> + <dataset name="role_without_subscription_permissions"> + <field name="rolename" xsi:type="string">RoleName%isolation%</field> + <field name="resource_access" xsi:type="string">Custom</field> + <field name="current_password" xsi:type="string">%current_password%</field> + <field name="roles_resources" xsi:type="array"> + <item name="Dashboard" xsi:type="string">Magento_Backend::dashboard</item> + <item name="Stores" xsi:type="string">Magento_Backend::stores</item> + <item name="Configuration" xsi:type="string">Magento_Config::config</item> + <item name="General Section" xsi:type="string">Magento_Config::config_general</item> + <item name="System" xsi:type="string">Magento_Backend::system</item> + <item name="Other Settings" xsi:type="string">Magento_Backend::system_other_settings</item> + <item name="Notifications" xsi:type="string">Magento_AdminNotification::adminnotification</item> + <item name="Show list" xsi:type="string">Magento_AdminNotification::show_list</item> + </field> + </dataset> + </repository> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/User.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/User.xml new file mode 100644 index 0000000000000000000000000000000000000000..13bbb4d1306c5284744c7466ea189ccec546c051 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Repository/User.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/Repository/etc/repository.xsd"> + <repository class="Magento\User\Test\Repository\User"> + <dataset name="custom_admin_with_role_without_subscription_permissions"> + <field name="username" xsi:type="string">AdminUser%isolation%</field> + <field name="firstname" xsi:type="string">FirstName%isolation%</field> + <field name="lastname" xsi:type="string">LastName%isolation%</field> + <field name="email" xsi:type="string">email%isolation%@example.com</field> + <field name="password" xsi:type="string">123123q</field> + <field name="password_confirmation" xsi:type="string">123123q</field> + <field name="role_id" xsi:type="array"> + <item name="dataset" xsi:type="string">role::role_without_subscription_permissions</item> + </field> + <field name="current_password" xsi:type="string">%current_password%</field> + <field name="is_active" xsi:type="string">Active</field> + </dataset> + </repository> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.php new file mode 100644 index 0000000000000000000000000000000000000000..970ce59ceb5bf240ba6a49390450e9f524c3cd00 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\TestCase; + +use Magento\Mtf\TestCase\Injectable; +use Magento\Backend\Test\Page\Adminhtml\Dashboard; + +/** + * Steps: + * 1. Log in to backend. + * 2. Click on Advanced Reporting link. + * 3. Perform asserts. + * + * @ZephyrId MAGETWO-63715 + */ +class AdvancedReportingButtonTest extends Injectable +{ + /* tags */ + const MVP = 'no'; + /* end tags */ + + /** + * Run menu navigation test. + * + * @param Dashboard $dashboard + * @return void + */ + public function test(Dashboard $dashboard) + { + $dashboard->open(); + $dashboard->getReportsSectionBlock()->click(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..a975d19ef8879f6625b3726c6447f06417751a47 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Analytics\Test\TestCase\AdvancedReportingButtonTest" summary="Navigate through Advanced Reporting button on dashboard to Sign Up page" ticketId="MAGETWO-63715"> + <variation name="AdvancedReportingButtonTest"> + <data name="advancedReportingLink" xsi:type="string">https://advancedreporting.rjmetrics.com/report</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertAdvancedReportingPage" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/CustomAclPermissionTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/CustomAclPermissionTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..a7b00bac17d255c36548147d0974d6fcd19f0131 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/CustomAclPermissionTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\User\Test\TestCase\CustomAclPermissionTest" summary="Advanced Reporting configuration permission" ticketId="MAGETWO-82648"> + <variation name="UserWithoutSubscriptionPermissionsTestVariation1" summary="User without subscription permissions"> + <data name="user/dataset" xsi:type="string">custom_admin_with_role_without_subscription_permissions</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertAdvancedReportingSectionInvisible" /> + </variation> + <variation name="UserWithSubscriptionPermissionsTestVariation2" summary="User with subscription permissions"> + <data name="user/dataset" xsi:type="string">custom_admin_with_default_role</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertAdvancedReportingSectionVisible" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/EnableDisableTest.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/EnableDisableTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b52fa92ad674362ad8f4843eb804698d2d411410 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/EnableDisableTest.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\TestCase; + +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; +use Magento\Mtf\TestCase\Injectable; + +/** + * Steps: + * 1. Login as admin user in backend + * 2. Navigate to menu Stores>Configuration>General>Advanced Reporting->General + * 3. Set Option "Advanced Reporting Service" + * 4. Click "Save Config" + * 5. Perform assertions + * + * @ZephyrId MAGETWO-66465 + */ +class EnableDisableTest extends Injectable +{ + /* tags */ + const MVP = 'no'; + const SEVERITY = 'S1'; + /* end tags */ + + /** + * @param ConfigAnalytics $configAnalytics + * @param string $vertical + * @param string $state + * @return void + */ + public function test(ConfigAnalytics $configAnalytics, $vertical, $state) + { + $configAnalytics->open(); + $configAnalytics->getAnalyticsForm()->analyticsToggle($state); + $configAnalytics->getAnalyticsForm()->setAnalyticsVertical($vertical); + $configAnalytics->getAnalyticsForm()->saveConfig(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/EnableDisableTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/EnableDisableTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..fdc92ed814a907197747be75e06b08f5a1bf1393 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/EnableDisableTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Analytics\Test\TestCase\EnableDisableTest" summary="Enable Disable Analytics" ticketId="MAGETWO-66465"> + <variation name="disableAnalytics"> + <data name="vertical" xsi:type="string">Apps and Games</data> + <data name="state" xsi:type="string">Disable</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsDisabled" /> + </variation> + <variation name="enableAnalytics"> + <data name="vertical" xsi:type="string">Apps and Games</data> + <data name="state" xsi:type="string">Enable</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsEnabled" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/InstallTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/InstallTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..28f861fce46d1d49444b9bfbb2d6c433560ba7a0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/InstallTest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Install\Test\TestCase\InstallTest" summary="[Web Setup][Auto] Install CE Magento via Web Interface"> + <variation name="InstallTestVariation" summary="Magento analytics opt-in by default" ticketId="MAGETWO-80760"> + <data name="tag" xsi:type="string">severity:S1</data> + <constraint name="Magento\User\Test\Constraint\AssertUserSuccessLogin" next="Magento\ReleaseNotification\Test\Constraint\AssertReleaseNotificationPopupExist"/> + <constraint name="Magento\ReleaseNotification\Test\Constraint\AssertReleaseNotificationPopupExist" prev="Magento\User\Test\Constraint\AssertUserSuccessLogin" next="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsEnabled"/> + <constraint name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsEnabled" prev="Magento\ReleaseNotification\Test\Constraint\AssertReleaseNotificationPopupExist" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..9c19f80e91d3982cc181639f794d3e1ab559dffa --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/NavigateMenuTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Backend\Test\TestCase\NavigateMenuTest" summary="Navigate to menu chapter"> + <variation name="NavigateMenuTestBIEssentials" summary="Navigate through BI Essentials admin menu to Sign Up page" ticketId="MAGETWO-63700"> + <data name="menuItem" xsi:type="string">Reports > BI Essentials</data> + <data name="waitMenuItemNotVisible" xsi:type="boolean">false</data> + <data name="businessIntelligenceLink" xsi:type="string">https://dashboard.rjmetrics.com/v2/magento/signup</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertBIEssentialsLink" /> + </variation> + <variation name="NavigateMenuTestAdvancedReporting" summary="Navigate through Advanced Reporting admin menu to BI Reports page" ticketId="MAGETWO-65748"> + <data name="menuItem" xsi:type="string">Reports > Advanced Reporting</data> + <data name="waitMenuItemNotVisible" xsi:type="boolean">false</data> + <data name="advancedReportingLink" xsi:type="string">https://advancedreporting.rjmetrics.com/report</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertAdvancedReportingPage" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetIndustryTest.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetIndustryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8b76df048b9a49ed00e0b6a4d30ce13f633a5e38 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetIndustryTest.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Test\TestCase; + +use Magento\Mtf\TestCase\Injectable; +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; + +/** + * Steps: + * 1. Log in to backend. + * 2. Navigate to analytics menu in system config + * 3. Select one of the verticals and save config + * 4. Perform assertions + * + * @ZephyrId MAGETWO-63898 + */ +class SetIndustryTest extends Injectable +{ + /* tags */ + const MVP = 'no'; + /* end tags */ + + /** + * Set analytics vertical test. + * + * @param ConfigAnalytics $configAnalytics + * @param string $industry + * @return void + */ + public function test(ConfigAnalytics $configAnalytics, $industry) + { + $configAnalytics->open(); + $configAnalytics->getAnalyticsForm()->setAnalyticsVertical($industry); + $configAnalytics->getAnalyticsForm()->saveConfig(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetIndustryTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetIndustryTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..3a04420ca9d64924c1a9e14cc399a677ec2eff04 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetIndustryTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Analytics\Test\TestCase\SetIndustryTest" summary="Navigate to Advanced Reporting admin menu and save industry" ticketId="MAGETWO-63898"> + <variation name="SetIndustry"> + <data name="industry" xsi:type="string">Apps and Games</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertIndustryIsSet" /> + <constraint name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsIndustryScope" /> + </variation> + <variation name="SetIndustryEmpty" summary="Navigate to Advanced Reporting admin menu and try to save empty industry" ticketId="MAGETWO-63981"> + <data name="industry" xsi:type="string">--Please Select--</data> + <data name="errorMessage" xsi:type="string">Please select a vertical.</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertEmptyIndustryCanNotBeSaved" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetTimeToSendDataTest.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetTimeToSendDataTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e05804c2bfcfb326a9745045296923f0936162df --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetTimeToSendDataTest.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\TestCase; + +use Magento\Analytics\Test\Page\Adminhtml\ConfigAnalytics; +use Magento\Mtf\TestCase\Injectable; +use Magento\Mtf\TestStep\TestStepFactory; + +/** + * Steps: + * 1. Login as admin user in backend + * 2. Navigate to menu Stores>Configuration>General>Advanced Reporting->General + * 3. Set Option "Time of day to send data" + * 4. Click "Save Config" + * 5. Perform assertions + * + * @ZephyrId MAGETWO-66464 + */ +class SetTimeToSendDataTest extends Injectable +{ + /* tags */ + const MVP = 'no'; + const SEVERITY = 'S1'; + /* end tags */ + + /** + * @var array + */ + private $configData; + + /** + * @param ConfigAnalytics $configAnalytics + * @param TestStepFactory $testStepFactory + * @param string $hh + * @param string $mm + * @param string $vertical + * @param string $configData + * @return void + */ + public function test( + ConfigAnalytics $configAnalytics, + TestStepFactory $testStepFactory, + $hh, + $mm, + $vertical, + $configData + ) { + $this->configData = $configData; + $testStepFactory->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData] + )->run(); + + $configAnalytics->open(); + $configAnalytics->getAnalyticsForm()->setAnalyticsVertical($vertical); + $configAnalytics->getAnalyticsForm()->setTimeOfDayToSendData($hh, $mm); + $configAnalytics->getAnalyticsForm()->saveConfig(); + } + + /** + * Clean data after running test. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'rollback' => true] + )->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetTimeToSendDataTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetTimeToSendDataTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..21cc1f732c1f87c74c2cc6124718c162ee943499 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/SetTimeToSendDataTest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Analytics\Test\TestCase\SetTimeToSendDataTest" summary="Time of day to send data" ticketId="MAGETWO-66464"> + <variation name="timeOfDayToSendDataVariation1"> + <data name="vertical" xsi:type="string">Apps and Games</data> + <data name="hh" xsi:type="string">11</data> + <data name="mm" xsi:type="string">11</data> + <data name="configData" xsi:type="string">change_default_timezone</data> + <constraint name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsSendingTimeAndZone" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestStep/OpenAnalyticsConfigStep.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestStep/OpenAnalyticsConfigStep.php new file mode 100644 index 0000000000000000000000000000000000000000..1f0a8ff4804a3a9acdf7d204004d44225120f98d --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestStep/OpenAnalyticsConfigStep.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Test\TestStep; + +use Magento\Backend\Test\Page\Adminhtml\Dashboard; +use Magento\Backend\Test\Page\Adminhtml\SystemConfigEdit; +use Magento\Mtf\TestStep\TestStepInterface; + +/** + * Steps: + * 1. Log in to backend. + * 2. Navigate to Stores->Configuration->General->Analytics->General menu. + */ +class OpenAnalyticsConfigStep implements TestStepInterface +{ + /** + * Dashboard page. + * + * @var Dashboard + */ + private $dashboard; + + /** + * System Config page. + * + * @var SystemConfigEdit + */ + private $systemConfigPage; + + /** + * @param Dashboard $dashboard + * @param SystemConfigEdit $systemConfigPage + */ + public function __construct(Dashboard $dashboard, SystemConfigEdit $systemConfigPage) + { + $this->dashboard = $dashboard; + $this->systemConfigPage = $systemConfigPage; + } + + /** + * Navigate to Stores->Configuration->General->Analytics->General menu. + * + * @return void + */ + public function run() + { + $this->dashboard->open(); + $this->dashboard->getMenuBlock()->navigate('Stores > Configuration'); + $this->systemConfigPage->getForm()->getGroup('analytics', 'general'); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/etc/di.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac51a3e2b6dd89a4e1b0644dccaa696ce1275ece --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsDisabled"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> + <type name="Magento\Analytics\Test\Constraint\AssertConfigAnalyticsEnabled"> + <arguments> + <argument name="severity" xsi:type="string">S1</argument> + </arguments> + </type> +</config> diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Block/Adminhtml/Dashboard/ReleaseNotification/ReleaseNotificationBlock.php b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Block/Adminhtml/Dashboard/ReleaseNotification/ReleaseNotificationBlock.php new file mode 100644 index 0000000000000000000000000000000000000000..e090d66e06a90e99769879b5875aa4a10dbe358f --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Block/Adminhtml/Dashboard/ReleaseNotification/ReleaseNotificationBlock.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Test\Block\Adminhtml\Dashboard\ReleaseNotification; + +use Magento\Ui\Test\Block\Adminhtml\Modal; + +/** + * Release notification block. + */ +class ReleaseNotificationBlock extends Modal +{ + /** + * @var string + */ + private $releaseNotificationText = '[data-index="release_notification_text"]'; + + /** + * @inheritdoc + */ + public function isVisible() + { + $this->waitModalAnimationFinished(); + return parent::isVisible() && $this->_rootElement->find($this->releaseNotificationText)->isVisible(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertLoginAgainAfterFlushCacheReleaseNotificationPopupExist.php b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertLoginAgainAfterFlushCacheReleaseNotificationPopupExist.php new file mode 100644 index 0000000000000000000000000000000000000000..798c17cd1d60885d02d1eb046cbaa93329a90bae --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertLoginAgainAfterFlushCacheReleaseNotificationPopupExist.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Test\Constraint; + +use Magento\Backend\Test\Page\Adminhtml\Dashboard; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\PageCache\Test\Page\Adminhtml\AdminCache; +use Magento\User\Test\Fixture\User; + +/** + * Assert that Release Notification Popup is visible on dashboard when admin user login again after flush cache + */ +class AssertLoginAgainAfterFlushCacheReleaseNotificationPopupExist extends AbstractConstraint +{ + /** + * Assert that Release Notification Popup is visibile on dashboard when admin user login again after flush cache + * + * @param Dashboard $dashboard + * @param User $user + * @param AdminCache $adminCache + * @return void + */ + public function processAssert(Dashboard $dashboard, User $user, AdminCache $adminCache) + { + // Flush cache + $adminCache->open(); + $adminCache->getActionsBlock()->flushMagentoCache(); + $adminCache->getMessagesBlock()->waitSuccessMessage(); + + // Log out + $dashboard->getAdminPanelHeader()->logOut(); + + // Log in again + $this->objectManager->create( + \Magento\User\Test\TestStep\LoginUserOnBackendStep::class, + ['user' => $user] + )->run(); + + \PHPUnit_Framework_Assert::assertTrue( + $dashboard->getReleaseNotificationBlock()->isVisible(), + "Release Notification Popup is absent on dashboard." + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return "Release Notification Popup is visible on dashboard."; + } +} diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertLoginAgainReleaseNotificationPopupNotExist.php b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertLoginAgainReleaseNotificationPopupNotExist.php new file mode 100644 index 0000000000000000000000000000000000000000..2bf6453c4379928903b0be4b1a0d579c708aee7b --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertLoginAgainReleaseNotificationPopupNotExist.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Test\Constraint; + +use Magento\Backend\Test\Page\Adminhtml\Dashboard; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\User\Test\Fixture\User; + +/** + * Assert that Release Notification Popup is absent on dashboard when admin user login again + */ +class AssertLoginAgainReleaseNotificationPopupNotExist extends AbstractConstraint +{ + /** + * Assert that Release Notification Popup is absent on dashboard when admin user login again + * + * @param Dashboard $dashboard + * @return void + */ + public function processAssert(Dashboard $dashboard, User $user) + { + $this->objectManager->create( + \Magento\User\Test\TestStep\LogoutUserOnBackendStep::class + )->run(); + + $this->objectManager->create( + \Magento\User\Test\TestStep\LoginUserOnBackendStep::class, + ['user' => $user] + )->run(); + + \PHPUnit_Framework_Assert::assertFalse( + $dashboard->getReleaseNotificationBlock()->isVisible(), + "Release Notification Popup is visible on dashboard." + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return "Release Notification Popup is absent on dashboard."; + } +} diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertReleaseNotificationPopupExist.php b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertReleaseNotificationPopupExist.php new file mode 100644 index 0000000000000000000000000000000000000000..9882d5e19842accf149d2bd7fd00a6d59f977144 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Constraint/AssertReleaseNotificationPopupExist.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Test\Constraint; + +use Magento\Backend\Test\Page\Adminhtml\Dashboard; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Assert that Release Notification Popup is visible on dashboard + */ +class AssertReleaseNotificationPopupExist extends AbstractConstraint +{ + /** + * Assert that release notificationt popup is visible on dashboard + * + * @param Dashboard $dashboard + * @return void + */ + public function processAssert(Dashboard $dashboard) + { + \PHPUnit_Framework_Assert::assertTrue( + $dashboard->getReleaseNotificationBlock()->isVisible(), + "Release Notification Popup is absent on dashboard." + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return "Release Notification Popup is visible on dashboard."; + } +} diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Page/Adminhtml/Dashboard.xml b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Page/Adminhtml/Dashboard.xml new file mode 100644 index 0000000000000000000000000000000000000000..5fc0122135a30dc8209b85fa553b137aeb0f68ef --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/Page/Adminhtml/Dashboard.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd"> + <page name="Dashboard" area="Adminhtml" mca="admin/dashboard" module="Magento_Backend"> + <block name="releaseNotificationBlock" class="Magento\ReleaseNotification\Test\Block\Adminhtml\Dashboard\ReleaseNotification\ReleaseNotificationBlock" locator="[data-index='release_notification_fieldset']" strategy="css selector" /> + <block name="modalBlock" class="Magento\Ui\Test\Block\Adminhtml\Modal" locator="._show[data-role=modal]" strategy="css selector" /> + </page> +</config> diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/TestCase/NotificationTest.php b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/TestCase/NotificationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b1478e1ed223a3c7a1d6610ab13ca8c1c2b782a5 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/TestCase/NotificationTest.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Test\TestCase; + +use Magento\Mtf\TestCase\Scenario; + +/** + * Preconditions: + * 1. Create admin user without permissions subscribe to Magento BI. + * + * Steps: + * 1. Login to the admin panel with the newly created admin user. + * 2. Navigate to dashboard. + * 3. Assert that release notification pop-up is visible. + * + * @ZephyrId MAGETWO-80786 + */ +class NotificationTest extends Scenario +{ + /* tags */ + const MVP = 'no'; + const SEVERITY = 'S1'; + /* end tags */ + + /** + * Test execution. + * + * @return void + */ + public function test() + { + $this->executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/TestCase/NotificationTest.xml b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/TestCase/NotificationTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7ba91e2c958d6a7420c701bce03defce66ebf36 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/TestCase/NotificationTest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\ReleaseNotification\Test\TestCase\NotificationTest" summary="Magento Release Notification Popup will appear to every admin user during the first login only" ticketId="MAGETWO-80786"> + <variation name="UserWithoutSubscriptionPermissionsTestVariation1" summary="User without subscription permissions"> + <data name="user/dataset" xsi:type="string">custom_admin_with_role_without_subscription_permissions</data> + <constraint name="Magento\ReleaseNotification\Test\Constraint\AssertReleaseNotificationPopupExist" /> + <constraint name="Magento\ReleaseNotification\Test\Constraint\AssertLoginAgainReleaseNotificationPopupNotExist" /> + </variation> + <variation name="UserWithSubscriptionPermissionsTestVariation2" summary="User with subscription permissions"> + <data name="user/dataset" xsi:type="string">custom_admin_with_default_role</data> + <constraint name="Magento\ReleaseNotification\Test\Constraint\AssertReleaseNotificationPopupExist" /> + <constraint name="Magento\ReleaseNotification\Test\Constraint\AssertLoginAgainReleaseNotificationPopupNotExist" /> + <constraint name="Magento\ReleaseNotification\Test\Constraint\AssertLoginAgainAfterFlushCacheReleaseNotificationPopupExist" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/etc/testcase.xml new file mode 100644 index 0000000000000000000000000000000000000000..4161d1432b7eabe67ecaa0897b52224db512810b --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ReleaseNotification/Test/etc/testcase.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/TestCase/etc/testcase.xsd"> + <scenario name="NotificationTest" firstStep="createUser"> + <step name="createUser" module="Magento_User" next="loginUserOnBackend" /> + <step name="loginUserOnBackend" module="Magento_User" /> + </scenario> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Search/Test/Constraint/AssertSynonymRestrictedAccess.php b/dev/tests/functional/tests/app/Magento/Search/Test/Constraint/AssertSynonymRestrictedAccess.php index a61c1fc06ba3e73ccee8fd8ff4e65b178a7d1578..26de8e0c76923a8bb2676e8d346df0a270b8d1de 100644 --- a/dev/tests/functional/tests/app/Magento/Search/Test/Constraint/AssertSynonymRestrictedAccess.php +++ b/dev/tests/functional/tests/app/Magento/Search/Test/Constraint/AssertSynonymRestrictedAccess.php @@ -18,7 +18,7 @@ class AssertSynonymRestrictedAccess extends AbstractConstraint /** * Access denied text. */ - const ACCESS_DENIED_TEXT = 'Access denied'; + const ACCESS_DENIED_TEXT = 'Sorry, you need permissions to view this content.'; /** * Assert that access to synonym group index page is restricted. diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php index 04ac3eee832458cfcfe30c9a61dd5a05665d4d2d..53c36e0a1e1b0a90cec432f129d54db772531bcb 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/TestCase/UpgradeSystemTest.php @@ -31,18 +31,23 @@ class UpgradeSystemTest extends Injectable protected $adminDashboard; /** - * Injection data. - * + * @var \Magento\Mtf\Util\Iterator\ApplicationState + */ + private $applicationStateIterator; + + /** * @param Dashboard $adminDashboard * @param SetupWizard $setupWizard - * @return void + * @param \Magento\Mtf\Util\Iterator\ApplicationState $applicationStateIterator */ public function __inject( Dashboard $adminDashboard, - SetupWizard $setupWizard + SetupWizard $setupWizard, + \Magento\Mtf\Util\Iterator\ApplicationState $applicationStateIterator ) { $this->adminDashboard = $adminDashboard; $this->setupWizard = $setupWizard; + $this->applicationStateIterator = $applicationStateIterator; } /** diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php index 558c1f224a1eec004600e1384219325d1a0769dd..138d4e810458195b4ac3baffc8dd713c6063f19f 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php +++ b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php @@ -16,7 +16,7 @@ use Magento\User\Test\Fixture\User; */ class AssertUserRoleRestrictedAccess extends AbstractConstraint { - const DENIED_ACCESS = 'Access denied'; + const DENIED_ACCESS = 'Sorry, you need permissions to view this content.'; /** * Asserts that user has only related permissions. diff --git a/dev/tests/integration/testsuite/Magento/Analytics/Model/Connector/Http/ReSignUpResponseResolverTest.php b/dev/tests/integration/testsuite/Magento/Analytics/Model/Connector/Http/ReSignUpResponseResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..91f2455c61d874e8bae0e63aa13e3e2f553a0df1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/Model/Connector/Http/ReSignUpResponseResolverTest.php @@ -0,0 +1,177 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Connector\Http; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\FlagManager; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Checks that cron job was set if error handler was set and appropriate http error code was returned. + */ +class ReSignUpResponseResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ResponseResolver + */ + private $otpResponseResolver; + + /** + * @var ResponseResolver + */ + private $updateResponseResolver; + + /** + * @var ConverterInterface + */ + private $converter; + + /** + * @var ResponseResolver + */ + private $notifyDataChangedResponseResolver; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @return void + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->otpResponseResolver = $objectManager->get( + 'OtpResponseResolver' + ); + $this->updateResponseResolver = $objectManager->get( + 'UpdateResponseResolver' + ); + $this->notifyDataChangedResponseResolver = $objectManager->get( + 'NotifyDataChangedResponseResolver' + ); + $this->converter = $objectManager->get(ConverterInterface::class); + $this->flagManager = $objectManager->get(FlagManager::class); + } + + /** + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * @magentoDbIsolation enabled + */ + public function testReSignUpOnOtp() + { + $body = $this->converter->toBody(['test' => '42']); + $retryResponse = new \Zend_Http_Response(401, [$this->converter->getContentTypeHeader()], $body); + $this->otpResponseResolver->getResult($retryResponse); + $this->assertCronWasSet(); + } + + /** + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * @magentoDbIsolation enabled + */ + public function testReSignOnOtpWasNotCalled() + { + $body = $this->converter->toBody(['test' => '42']); + $successResponse = new \Zend_Http_Response(201, [$this->converter->getContentTypeHeader()], $body); + $this->otpResponseResolver->getResult($successResponse); + $this->assertCronWasNotSet(); + } + + /** + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * @magentoDbIsolation enabled + */ + public function testReSignUpOnUpdateWasCalled() + { + $body = $this->converter->toBody(['test' => '42']); + $retryResponse = new \Zend_Http_Response(401, [$this->converter->getContentTypeHeader()], $body); + $this->updateResponseResolver->getResult($retryResponse); + $this->assertCronWasSet(); + } + + /** + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * @magentoDbIsolation enabled + */ + public function testReSignUpOnUpdateWasNotCalled() + { + $body = $this->converter->toBody(['test' => '42']); + $successResponse = new \Zend_Http_Response(201, [$this->converter->getContentTypeHeader()], $body); + $this->updateResponseResolver->getResult($successResponse); + $this->assertCronWasNotSet(); + } + + /** + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * @magentoDbIsolation enabled + */ + public function testReSignUpOnNotifyDataChangedWasNotCalledWhenSubscriptionUpdateIsRunning() + { + $this->flagManager + ->saveFlag( + SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, + 'https://previous.example.com/' + ); + $body = $this->converter->toBody(['test' => '42']); + $retryResponse = new \Zend_Http_Response(401, [$this->converter->getContentTypeHeader()], $body); + $this->notifyDataChangedResponseResolver->getResult($retryResponse); + $this->assertCronWasNotSet(); + } + + /** + * @return string|null + */ + private function getSubscribeSchedule() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** + * @var $scopeConfig ScopeConfigInterface + */ + $scopeConfig = $objectManager->get(ScopeConfigInterface::class); + + return $scopeConfig->getValue( + SubscriptionHandler::CRON_STRING_PATH, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + 0 + ); + } + + /** + * @return int|null + */ + private function getAttemptFlag() + { + $objectManager = Bootstrap::getObjectManager(); + /** + * @var $flagManager FlagManager + */ + $flagManager = $objectManager->get(FlagManager::class); + + return $flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + } + + /** + * @return void + */ + private function assertCronWasSet() + { + $this->assertEquals('0 * * * *', $this->getSubscribeSchedule()); + $this->assertGreaterThan(1, $this->getAttemptFlag()); + } + + /** + * @return void + */ + private function assertCronWasNotSet() + { + $this->assertNull($this->getSubscribeSchedule()); + $this->assertNull($this->getAttemptFlag()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Analytics/Model/Plugin/BaseUrlConfigPluginTest.php b/dev/tests/integration/testsuite/Magento/Analytics/Model/Plugin/BaseUrlConfigPluginTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b8933cb5ed3d291e49ad6ed6acb27ddda4a93dbe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/Model/Plugin/BaseUrlConfigPluginTest.php @@ -0,0 +1,207 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Analytics\Model\Plugin; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Config\Model\PreparedValueFactory; +use Magento\Config\Model\ResourceModel\Config\Data as ConfigData; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoAppArea adminhtml + */ +class BaseUrlConfigPluginTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var PreparedValueFactory + */ + private $preparedValueFactory; + + /** + * @var ConfigData + */ + private $configValueResourceModel; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->preparedValueFactory = $this->objectManager->get(PreparedValueFactory::class); + $this->configValueResourceModel = $this->objectManager->get(ConfigData::class); + $this->scopeConfig = $this->objectManager->get(ScopeConfigInterface::class); + $this->flagManager = $this->objectManager->get(FlagManager::class); + } + + /** + * @magentoDbIsolation enabled + */ + public function testAfterSaveNotSecureUrl() + { + $this->saveConfigValue( + Store::XML_PATH_UNSECURE_BASE_URL, + 'http://store.com/', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + $this->assertCronWasNotSet(); + } + + /** + * @magentoDbIsolation enabled + */ + public function testAfterSaveSecureUrlNotInDefaultScope() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'https://store.com/', + ScopeInterface::SCOPE_STORES + ); + $this->assertCronWasNotSet(); + } + + /** + * @magentoDbIsolation enabled + * @magentoAdminConfigFixture web/secure/base_url https://previous.example.com/ + */ + public function testAfterSaveSecureUrlInDefaultScopeOnDoesNotRegisteredInstance() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'https://store.com/', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + $this->assertCronWasNotSet(); + } + + /** + * @magentoDbIsolation enabled + * @magentoAdminConfigFixture web/secure/base_url https://previous.example.com/ + * @magentoAdminConfigFixture analytics/general/token MBI_token + */ + public function testAfterSaveSecureUrlInDefaultScopeOnRegisteredInstance() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'https://store.com/', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + $this->assertCronWasSet(); + } + + /** + * @magentoDbIsolation enabled + * @magentoAdminConfigFixture web/secure/base_url https://previous.example.com/ + * @magentoAdminConfigFixture analytics/general/token MBI_token + */ + public function testAfterSaveMultipleBaseUrlChanges() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'https://store.com/', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'https://store10.com/', + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + $this->assertCronWasSet(); + } + + /** + * @param string $path The configuration path in format section/group/field_name + * @param string $value The configuration value + * @param string $scope The configuration scope (default, website, or store) + * @return void + */ + private function saveConfigValue(string $path, string $value, string $scope) + { + $value = $this->preparedValueFactory->create( + $path, + $value, + $scope + ); + $this->configValueResourceModel->save($value); + } + + /** + * @return void + */ + private function assertCronWasNotSet() + { + $this->assertNull($this->getSubscriptionUpdateSchedule()); + $this->assertNull($this->getPreviousUpdateUrl()); + $this->assertNull($this->getUpdateReverseCounter()); + } + + /** + * @return void + */ + private function assertCronWasSet() + { + $this->assertSame( + '0 * * * *', + $this->getSubscriptionUpdateSchedule(), + 'Subscription update schedule has not been set' + ); + $this->assertSame( + 'https://previous.example.com/', + $this->getPreviousUpdateUrl(), + 'The previous URL stored for update is not correct' + ); + $this->assertSame(48, $this->getUpdateReverseCounter()); + } + + /** + * @return mixed + */ + private function getSubscriptionUpdateSchedule() + { + return $this->scopeConfig->getValue( + SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + } + + /** + * @return mixed + */ + private function getPreviousUpdateUrl() + { + return $this->flagManager->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE); + } + + /** + * @return mixed + */ + private function getUpdateReverseCounter() + { + return $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Analytics/Model/ReportUrlProviderTest.php b/dev/tests/integration/testsuite/Magento/Analytics/Model/ReportUrlProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0e2f8c4cc96a2f23365cb102fe27f0f304ee0d74 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/Model/ReportUrlProviderTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Analytics\Model; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Exception\State\SubscriptionUpdateException; +use Magento\Framework\FlagManager; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoAppArea adminhtml + */ +class ReportUrlProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ReportUrlProvider + */ + private $reportUrlProvider; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @return void + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->reportUrlProvider = $objectManager->get(ReportUrlProvider::class); + $this->flagManager = $objectManager->get(FlagManager::class); + } + + /** + * @magentoDbIsolation enabled + */ + public function testGetUrlWhenSubscriptionUpdateIsRunning() + { + $this->flagManager + ->saveFlag( + SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE, + 'https://previous.example.com/' + ); + $this->expectException(SubscriptionUpdateException::class); + $this->reportUrlProvider->getUrl(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Analytics/_files/create_link.php b/dev/tests/integration/testsuite/Magento/Analytics/_files/create_link.php new file mode 100644 index 0000000000000000000000000000000000000000..928bb6fb36a06b7f6d0798a1c6bad66f2ffde64f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/_files/create_link.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** + * @var $fileInfoManager \Magento\Analytics\Model\FileInfoManager + */ +$fileInfoManager = $objectManager->create(\Magento\Analytics\Model\FileInfoManager::class); + +/** + * @var $fileInfo \Magento\Analytics\Model\FileInfo + */ +$fileInfo = $objectManager->create( + \Magento\Analytics\Model\FileInfo::class, + ['path' => 'analytics/jsldjsfdkldf/data.tgz', 'initializationVector' => 'binaryDataisdodssds8iui'] +); + +$fileInfoManager->save($fileInfo); diff --git a/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php new file mode 100644 index 0000000000000000000000000000000000000000..0106bf6f1bdac15e590d6028a4bdc3e0089c340c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** + * @var $configWriter \Magento\Framework\App\Config\Storage\WriterInterface + */ +$configWriter = $objectManager->get(\Magento\Framework\App\Config\Storage\WriterInterface::class); + +$configWriter->delete(SubscriptionHandler::CRON_STRING_PATH); +$configWriter->save('analytics/subscription/enabled', 1); + +/** + * @var $analyticsToken \Magento\Analytics\Model\AnalyticsToken + */ +$analyticsToken = $objectManager->get(\Magento\Analytics\Model\AnalyticsToken::class); +$analyticsToken->storeToken('42'); + +/** + * @var $flagManager \Magento\Framework\FlagManager + */ +$flagManager = $objectManager->get(\Magento\Framework\FlagManager::class); + +$flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); diff --git a/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..3fd3e21e282e0c425fa69ab8a02fa03f5cea2c96 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** + * @var $configWriter \Magento\Framework\App\Config\Storage\WriterInterface + */ +$configWriter = $objectManager->get(\Magento\Framework\App\Config\Storage\WriterInterface::class); + +$configWriter->delete(SubscriptionHandler::CRON_STRING_PATH); +$configWriter->save('analytics/subscription/enabled', 0); + +/** + * @var $analyticsToken \Magento\Analytics\Model\AnalyticsToken + */ +$analyticsToken = $objectManager->get(\Magento\Analytics\Model\AnalyticsToken::class); +$analyticsToken->storeToken(null); + +/** + * @var $flagManager \Magento\Framework\FlagManager + */ +$flagManager = $objectManager->get(\Magento\Framework\FlagManager::class); + +$flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); diff --git a/dev/tests/integration/testsuite/Magento/ReleaseNotification/Model/ResourceModel/Viewer/LoggerTest.php b/dev/tests/integration/testsuite/Magento/ReleaseNotification/Model/ResourceModel/Viewer/LoggerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..930e7fe5317f8dd9d6f7c12454c6acad79d76bff --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ReleaseNotification/Model/ResourceModel/Viewer/LoggerTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ReleaseNotification\Model\ResourceModel\Viewer; + +use Magento\ReleaseNotification\Model\Viewer\Log; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoDbIsolation enabled + */ +class LoggerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Logger + */ + private $logger; + + /** + * @return void + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->logger = $objectManager->get(Logger::class); + } + + /** + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testLogAndGet() + { + $userModel = Bootstrap::getObjectManager()->get(\Magento\User\Model\User::class); + $adminUserNameFromFixture = 'adminUser'; + $adminUserId = $userModel->loadByUsername($adminUserNameFromFixture)->getId(); + $this->assertEmpty($this->logger->get($adminUserId)->getId()); + $firstLogVersion = '2.2.2'; + $this->logger->log($adminUserId, $firstLogVersion); + $firstLog = $this->logger->get($adminUserId); + $this->assertInstanceOf(Log::class, $firstLog); + $this->assertEquals($firstLogVersion, $firstLog->getLastViewVersion()); + $this->assertEquals($adminUserId, $firstLog->getViewerId()); + + $secondLogVersion = '2.3.0'; + $this->logger->log($adminUserId, $secondLogVersion); + $secondLog = $this->logger->get($adminUserId); + $this->assertInstanceOf(Log::class, $secondLog); + $this->assertEquals($secondLogVersion, $secondLog->getLastViewVersion()); + $this->assertEquals($adminUserId, $secondLog->getViewerId()); + $this->assertEquals($firstLog->getId(), $secondLog->getId()); + } + + /** + * @expectedException \Zend_Db_Statement_Exception + */ + public function testLogNonExistUser() + { + $this->logger->log(200, '2.2.2'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Iframe/ShowTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Iframe/ShowTest.php index 0bd2a74e0d373322a834a51f75f4cbdedc7ad018..415ea79be9f0712cb45f110819c45f4ff2628cef 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Iframe/ShowTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Controller/Adminhtml/Iframe/ShowTest.php @@ -25,7 +25,7 @@ class ShowTest extends \Magento\TestFramework\TestCase\AbstractBackendController $this->dispatch('backend/swatches/iframe/show/'); $this->assertEquals(200, $this->getResponse()->getHttpResponseCode()); - $this->assertNotContains('Access denied', $this->getResponse()->getBody()); + $this->assertNotContains('Sorry, you need permissions to view this content.', $this->getResponse()->getBody()); } /** @@ -43,6 +43,6 @@ class ShowTest extends \Magento\TestFramework\TestCase\AbstractBackendController $this->dispatch('backend/swatches/iframe/show/'); $this->assertEquals(403, $this->getResponse()->getHttpResponseCode()); - $this->assertContains('Access denied', $this->getResponse()->getBody()); + $this->assertContains('Sorry, you need permissions to view this content.', $this->getResponse()->getBody()); } }